c++模板和重载函数的二义调用

c++ templates and ambiguous call to overloaded function

本文关键字:调用 函数 重载 c++      更新时间:2023-10-16

我被下面的问题卡住了。我使用的是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中的一个错误。我要试着举报。(我非常喜欢visual studio的第一周)

更新3:

https://connect.microsoft.com/VisualStudio/feedback/details/3076577

functions.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
}

您打了这个电话:

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

地点:

  • gG<BX< int >, int>
  • dtDT<BX, int>*类型
  • bxBX< int >类型

现在你有了这两个函数:

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<BX< int >, int>
  • 第二个函数重载有一个应该匹配的模板化类型。
    • T可以由bx推导为int
    • T1<T>是模板模板类型,由bx推导为BX< int >
    • M将匹配任何内容。
    • 最后,第一个参数推导为G<BX< int >, int>与第一个函数
  • 相同

GCC也会抛出歧义错误。


要在传递G<...>类型时优先考虑第二个重载函数,需要使用部分专门化。(因为它们的排名高于主模板)。请参阅Sam Varshavchik给出了一种可能的方法。

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

…这是行不通的

那么,该做什么,该做什么……那么,如何将部分函数专门化转换为普通模板类专门化呢?

我的解决方案特化了第一个函数参数类型的模板,以便消除静态类的歧义,并将其转发给两个最终类方法之一。

一个好的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;
}

这看起来像是MSVC和GCC中的一个bug。调用应该解析为第二个重载(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<T2<U>, V> works形式的参数推导出M;M推导为G<T2<U>, V>
    • M形式的参数推导G<T2<U>, V>中的T2, UV不起作用(M可以是任何东西)。

    换句话说,G<T2<U>, V>是比M"更具体"的形式;它不能表示M所能表示的所有类型;这是更专业的试图在此上下文中形式化的直观含义。

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

这意味着与第二个模板对应的专门化在重载解析中是首选的。