【问题标题】:c++ templates and ambiguous call to overloaded functionc ++模板和对重载函数的模棱两可的调用
【发布时间】:2016-12-27 20:42:06
【问题描述】:

我遇到了以下问题。我使用的是 Xcode 7,我的项目没有问题。尝试在 Visual Studio Express 2015 上编译它后,我在代码中收到消息 "Error C2668 ambiguous call to overloaded function"。我找不到与该问题相关的特定于 Visual Studio 的任何内容。

我做了一个应该在 VS 上使用的最小工作示例(Xcode 上没有错误)。奇怪的部分涉及func2 部分。就好像 VS 编译器无法自动推断出比限制更多的类型和/或参数。

更新:

Sam Varshavchik 提出了一种解决方法,即使用带有中间模板类的部分特化。这是我想避免的解决方案。首先是因为它在我的代码中应用的上下文中不方便,其次是因为我不清楚这个编译错误。这个错误在 Xcode7 中没有出现,func2 甚至在 VS 中也没有错误。尽管我同意 WhiZTiM 的解释,但事实是,在这种情况下,重载有时可行,有时不行。我真的很想知道为什么。

更新 2:

根据 bogdan 的说法,这可能是 GCC 和 MSVC 中的一个错误。我会尝试报告它。 (我非常喜欢视觉工作室的第一周)

更新 3:

https://connect.microsoft.com/VisualStudio/feedback/details/3076577 报告的错误

函数.h:

template <class T>
class BX {
public :
    BX() {}
    ~BX() {}
};

template <class T1, class T2>
class G {
public :
    G() {}
    ~G() {}
};

template <template <class T> class T1, class T>
class DT {};

class B {
public :
    //I want func to work
    template <template <class T> class T1, class T, class M>
    static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}

    template <template <class T> class T1, class T, class M>
    static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}

    //here is a small variation of func as a test
    template <template <class T> class T1, class T, class M>
    static void func2(const M& m, const DT<T1, T>* dt) {}

    template <template <class T> class T1, class T, class M>
    static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};

main.cpp

int main() {
    BX< int > bx;
    G<BX< int >, int> g;
    DT<BX, int>* dt;
    B::func(g, dt, bx);//Error  C2668   'B::func': ambiguous call to overloaded function
    B::func2(g, dt);//no error
}

【问题讨论】:

  • 请不要发布带有无效void main 的代码示例。它会误导新手,并且需要在尝试之前编辑代码。 FTFY。
  • 这看起来像是一个 MSVC(和 GCC)错误。根据函数模板的部分排序规则,第二个模板重载比第一个更专业。这就是为func2 正确选择第二个重载的原因。重载分辨率对于func 应该同样有效。该示例应进一步减少并报告为错误。
  • @bogdan,你测试了吗?我很担心整个行为只会出现在我的系统上。此外,减少示例很棘手,实际上我已经为 MWE 做过。我的代码中实际等效的 func 比本主题中提供的 func 更复杂。我减少了它,直到我发现另一个'sistership' func2 编译正常。简而言之:减少 func 使其编译。
  • 是的,它可以在 MSVC 2015 Update 3 上完全重现。通过减少它,我的意思是类似于 this
  • 好的,感谢其他 MWE。我尝试向微软报告它,但我显然不允许报告错误。你认为我应该使用gcc.gnu.org/bugzilla 直接向 gcc 报告吗?我以前从来没有这样做过。我还想知道,如果我希望尽快修复这个错误,哪种方式应该是最快的,gcc 还是 microsoft?但也许这种事情需要数年才能得到解决

标签: c++ visual-studio templates overloading


【解决方案1】:

你打了这个电话:

B::func(g, dt, bx);

地点:

  • gG&lt;BX&lt; int &gt;, int&gt; 类型
  • dtDT&lt;BX, int&gt;* 类型
  • bxBX&lt; int &gt; 类型

现在你有了这两个函数:

template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
               //^^^^^^^^^^

template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
               //^^^^^^^^^^^^^^^^^^

在重载决议期间;并只考虑函数声明中的第一个参数(因为这些参数据说是使函数声明不同的原因):

  • 第一个函数重载中的M 被推断为G&lt;BX&lt; int &gt;, int&gt;
  • 第二个函数重载具有应匹配的模板类型。
    • T 可以从 bx 推导出为 int
    • T1&lt;T&gt; 模板模板类型,由bx 推导出为BX&lt; int &gt;
    • M 将匹配任何内容。
    • 在一天结束时,您将第一个参数推导出为 G&lt;BX&lt; int &gt;, int&gt;,这与第一个函数的相同

GCC 也会引发歧义错误。


要在传递G&lt;...&gt; 类型时优先使用第二个重载函数,您需要使用部分特化。 (因为它们的排名高于主要模板)。请参见 Sam Varshavchik's answer 了解这样做的一种可能方式。

【讨论】:

  • 这个分析是正确的。然而,这个答案缺少的是一个解决方案。
  • @SamVarshavchik,哈哈。我仍在编辑我的答案。尽管如此,我在这里应用了“分工”。我已对可能的解决方案进行了简要总结,并参考了您的答案以进行实施。 :-)。
  • 我确实同意这个诊断。但是为什么 func2 没有引起任何歧义呢?
  • 另外,为什么它在 Xcode7 上编译良好,并在运行时提供预期的行为?
【解决方案2】:

所示代码的明显意图是部分函数特化。

哪个...行不通。

那么,怎么办,怎么办……好吧,将偏函数特化转化为普通模板类特化怎么样?

我的解决方案专门针对第一个函数参数类型使用模板,以消除静态类的歧义,并将其转发给两个最终类方法之一。

一个好的 C++ 编译器应该能够优化掉额外的函数调用层:

template <class T>
class BX {
public :
    BX() {}
    ~BX() {}
};

template <class Tdata, class Tmetric>
class G {
public :
    G() {}
    ~G() {}
};

template <template <class T> class T1, class T>
class DT {};

template<class M> class B_func;

class B {
public :
    template <template <class T> class T1, class T, class M>
    static void func(const M& m, const DT<T1, T>* dt, T1<T>& val)
    {
        B_func<M>::func(m, dt, val);
    }

    template <template <class T> class T1, class T, class M>
    static void func_a(const M& m, const DT<T1, T>* dt, T1<T>& val) {}

    template <template <class T> class T1, class T, class M>
    static void func_b(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}

    //here is a small variation of func as a test
    template <template <class T> class T1, class T, class M>
    static void func2(const M& m, const DT<T1, T>* dt) {}

    template <template <class T> class T1, class T, class M>
    static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};


template <class M>
class B_func {
public:
    template<class two, class three>
    static void func(const M& m, const two* dt, three& val)
    {
        B::func_a(m, dt, val);
    }
};

template <template <class T> class T1, class T, class M>
class B_func<G<T1<T>, M>> {
public:
    template<class two, class three>
    static void func(const G<T1<T>, M>& m, const two* dt, three& val)
    {
        B::func_b(m, dt, val);
    }
};


int main() {
    BX< int > bx;
    G<BX< int >, int> g;
    DT<BX, int>* dt;
    B::func(g, dt, bx);
    B::func2(g, dt);

    return 0;
}

【讨论】:

  • 谢谢,添加结构/类专业化层确实是一个解决方案。我还没有测试它,但我很确定它按预期工作,因为我也在代码的其他部分使用它。但是我想完全理解为什么 XCode7 和 VS2015 在编译时会有不同的解释。以及为什么 func2 不会像 func 提出一个那样提出这种歧义。
  • 另外,具体来说,我并不是要部分专门化模板函数,而是重载它。
【解决方案3】:

这看起来像是 MSVC 和 GCC 中的错误。该调用应解析为第二个重载(Clang 和 EDG 正在这样做)。

对于调用 B::func(g, dt, bx),名称查找会找到两个 func 模板。对它们中的每一个都执行模板参数推导和替换,以生成可以随后参与重载决议的函数模板特化声明。两个模板的推导都成功了,我们剩下两个特化:

void B::func<BX, int, G<BX<int>, int>>(const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
void B::func<BX, int, int>            (const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);

这两个函数具有相同的参数声明子句,因此很明显重载解析无法根据调用参数的转换来区分它们;它必须求助于该过程的最后两个步骤。

首先,如果其中一个函数是模板特化而另一个不是,则首选非模板函数;此处不适用。

最后,它查看合成两个特化声明的模板;如果根据功能模板的偏序,其中一个模板比另一个模板更特化,则首选相应的特化。 (这是原始模板重新发挥作用的过程中唯一的地方。)

下面的描述不是很准确,并且跳过了很多细节,但我试图专注于与此案例相关的部分。非常粗略:

  • 首先,从两个模板的函数参数声明中剥离引用和​​ cv 限定符,产生:

    F1(M          , const DT<T1, T>*, T1<T>)
    F2(G<T2<U>, V>, const DT<T2, U>*, T2<U>)
    

    (模板参数名称已更改以避免混淆)

  • 然后,尝试推导,就好像使用另一个模板的函数参数的形式作为参数调用一个模板,然后反过来。在这种情况下,最后两对对应的参数具有相同的形式,因此两种方式都可以推演。对于第一对对应的参数:

    • G&lt;T2&lt;U&gt;, V&gt; 形式的参数推导出M 有效; M 推导出为G&lt;T2&lt;U&gt;, V&gt;
    • M 形式的参数推导G&lt;T2&lt;U&gt;, V&gt; 中的T2UV 不起作用(M 可以是任何东西)。

    换句话说,G&lt;T2&lt;U&gt;, V&gt; 是比M“更具体”的形式;它不能代表M可以代表的所有类型;这是更专业试图在这种情况下形式化的直观含义。

  • 因此,推论适用于从F2F1 的所有对应参数对,但反之则不行。这使得F2 在偏序方面比F1 更专业。

这意味着与第二个模板对应的特化在重载决策中是首选的。

【讨论】:

    猜你喜欢
    • 2015-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多