【问题标题】:Overload resolution of function templates in VS2013 RCVS2013 RC中函数模板的过载解决
【发布时间】:2013-09-29 21:51:15
【问题描述】:

在VS2012中编译以下代码没有任何问题。

struct Foo
{
    typedef int DummyType;
};

template<typename T>
int Bar(T* foo, typename T::DummyType* dummy_ = 0) { return 0; }

template<typename T>
int Bar(T* foo, ...) { return 1; }

template<typename T>
int Bar(typename T::DummyType* dummy_ = 0) { return 2; }

template<typename T>
int Bar(...) { return 3; }


void fn()
{
    Bar((Foo*)NULL);
    Bar((int*)NULL);

    Bar<Foo>();
    Bar<int>();
}

但尝试 VS2013RC 时出现以下错误。这是 VS2013RC 错误还是代码本身的问题。标准中关于匹配重载函数与模板函数特化和可变参数函数的内容。

1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(25): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(15): could be 'int Bar<Foo>(T *,...)'
1>          with
1>          [
1>              T=Foo
1>          ]
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(12): or       'int Bar<Foo>(T *,Foo::DummyType *)'
1>          with
1>          [
1>              T=Foo
1>          ]
1>          while trying to match the argument list '(Foo *)'
1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(28): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(21): could be 'int Bar<Foo>(...)'
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(18): or       'int Bar<Foo>(Foo::DummyType *)'
1>          while trying to match the argument list '()'

感谢您的帮助!


感谢您的回答!

我刚刚做了一个新的测试如下:

struct Foo
{
    typedef int DummyType;
};

// Bar0 #0
template<typename T>
static int Bar0(const T* foo, typename T::DummyType* dummy_) { return 0; }

// Bar0 #1
template<typename T>
static int Bar0(const T* foo, ...) { return 1; }


template<typename T, typename U>
struct DummyType2 {};

// Bar1 #2
template<typename T>
static int Bar1(const T* foo, DummyType2<T, typename T::DummyType>* dummy_) { return 2; }

// Bar1 #3
template<typename T>
static int Bar1(const T* foo, ...) { return 3; }

void fn()
{

    std::cout<<Bar0((Foo*)NULL, NULL)<<std::endl;   // call 0 matches Bar0 #0

    std::cout<<Bar1((Foo*)NULL, NULL)<<std::endl;   // call 1 matches Bar1 #3
}

输出是

0
3

调用 0 匹配 Bar0 #0,但调用 1 匹配 Bar1 #3 的原因是什么。标准中的任何规则?

【问题讨论】:

    标签: c++ templates ambiguous overload-resolution visual-studio-2013


    【解决方案1】:

    对这四个重载进行编号以供参考:

    template<typename T>
    int Bar(T*, typename T::DummyType* = 0);             // #1
    
    template<typename T>
    int Bar(T*, ...);                                    // #2
    
    template<typename T>
    int Bar(typename T::DummyType* = 0);                 // #3
    
    template<typename T>
    int Bar(...);                                        // #4
    

    根据 [temp.deduct.type]/5,typename T::DummyTypeT 的非推断上下文。即参数typename T::DummyType* dummy_不能用于推导T。因此,对于前两个调用

    Bar((Foo*)NULL);
    Bar((int*)NULL);
    

    T 可以推导出前两个重载,但不能推导出后两个。这就是为什么重载 #3 和 #4 对于这些调用不可行的原因。在这个推导之后,函数签名中每次出现的T 都将替换为推导的类型。这可能会导致替换失败,请参阅下面的调用 2。


    对于第一次调用,以下两个重载是可行的:

    /*substituted template*/
    int Bar<Foo>(Foo*, Foo::DummyType* = 0);             // #1
    
    /*substituted template*/
    int Bar<Foo>(Foo*, ...);                             // #2
    

    根据 [over.match.viable]/2,对于重载解析,默认参数将被忽略:

    首先,要成为一个可行的函数,候选函数应有足够的参数,以在数量上与列表中的参数一致。

    • 如果列表中有 m 个参数,则所有具有恰好 m 个参数的候选函数都是可行的。
    • 参数少于 m 个的候选函数只有在其参数列表中有省略号时才可行 (8.3.5)。出于重载决议的目的,没有对应参数的任何参数都被视为“匹配省略号”(13.3.3.1.3)。
    • 仅当 (m+1)-st 参数具有默认参数 (8.3.6) 时,具有多个 m 参数的候选函数才是可行的。出于重载解析的目的,参数列表在右侧被截断,因此恰好有 m 个参数。

    所以,我们实际上在这里比较这两个签名:

    /*substituted template*/
    int Bar<Foo>(Foo*);                                 // #1
    
    /*substituted template*/
    int Bar<Foo>(Foo*);                                 // #2
    

    这两个属于完全匹配,因此是模棱两可的。


    对于第二次调用,我们的第一次重载替换失败(见下文),因此它不在可行函数列表中。只有一个过载仍然可行:

    /*substituted template*/
    int Bar<int>(int*);                                 // #2
    

    替换失败:

    对于第二次调用Bar((int*)NULL);,将T 替换为int 会导致第一次重载[temp.deduct]/5 中的替换失败:

    当所有模板实参都从默认模板实参推导出或获得时,模板的模板形参列表和函数类型中所有模板形参的使用都替换为对应的推导或默认实参值。如果替换导致无效类型,如上所述,类型推导失败。

    这里的无效类型是int::DummyType


    对于第三次和第四次调用,只有最后两个重载是可行的(因为参数的数量)。其余的与前两个重载类似。

    第三次调用必须从重载中选择

    /*substituted template*/
    int Bar<Foo>(Foo::DummyType* = 0);                   // #3
    
    /*substituted template*/
    int Bar<Foo>(...);                                   // #4
    

    这和第一次通话一样模棱两可。


    对于第四次调用,第三次重载导致替换失败,只有第四次重载仍然可行(并且被明确选择)。


    跟进问题。

    第一个电话:Bar0((Foo*)NULL, NULL)

    重载:

    // Bar0 #0
    template<typename T>
    static int Bar0(const T* foo, typename T::DummyType* dummy_);
    
    // Bar0 #1
    template<typename T>
    static int Bar0(const T* foo, ...);
    

    Bar0 #0 中,T 再次处于非推导上下文中,因此只有第一个参数用于推导。替换的模板签名如下所示:

    // substituted template
    static int Bar0<Foo>(const Foo*, Foo::DummyType*);  // #0
    
    // substituted template
    static int Bar0<Foo>(const Foo* foo, ...);          // #1
    

    NULL 的定义现在变得有些相关:

    [support.types]/3

    NULL 是本国际标准中实现定义的 C++ 空指针常量。

    [conv.ptr]/1

    空指针常量是整数类型的整数常量表达式 (5.19) 纯右值,其计算结果为零或std::nullptr_t 类型的纯右值。空指针常量可以转换为指针类型;结果是该类型的 空指针值,并且可以与对象指针或函数指针类型的所有其他值区分开来。

    没有指定NULL 的确切类型(另一个使用nullptr 的理由!)。但我们知道它可以转换为Foo::DummyType*。此转换是标准转换。将NULL与省略号匹配就是所谓的省略号转换;这并不是真正的转换,只是在重载分辨率方面 [over.ics.ellipsis]/1:

    当函数调用中的参数与被调用函数的省略号参数规范匹配时,就会发生省略号转换序列(参见 5.2.2)。

    现在,这两个重载是可行的,必须进行排名。幸运的是,这里很简单[over.ics.rank]/2

    标准转换序列是比用户定义的转换序列或省略号转换序列更好的转换序列

    因此,与匹配@987654351 的省略号转换序列相比,重载#0 所需的类型NULLFoo::DummyType* 的转换序列是更好的转换序列@ 与重载 #2 的 ...

    [over.match.best] 现在指定所选函数是具有最佳转换顺序的函数。因此,明确选择重载#0。


    第二次通话:Bar1((Foo*)NULL, NULL)

    重载:

    // Bar1 #2
    template<typename T>
    static int Bar1(const T*, DummyType2<T, typename T::DummyType>*);
    
    // Bar1 #3
    template<typename T>
    static int Bar1(const T*, ...);
    

    这里,重要的部分是DummyType2&lt;T, ..&gt; 中的T。它在非推断上下文中是 not 的。因此,编译器尝试从第一个第二个参数推导出T。由于调用中的第二个参数有一些未指定的整数或std::nullptr_t 类型,因此类型推导因重载Bar1 #2 而失败。重载Bar1 #3 仍然可行并且被明确选中。

    但是,如果您将重载 Bar1 #2 更改为:

    // Bar1 #2
    template<typename T>
    static int Bar1(const T*, DummyType2<int, typename T::DummyType>*);
    

    然后T 仅从第一个参数推导出来,并且优先选择此重载(与第一次调用后续问题的原因相同)。

    您还可以(而不是更改重载)将第二次调用更改为:

    Bar1((Foo*)NULL, (DummyType2<Foo, int>*)NULL)
    

    这样,T 可以明确推导出为Foo,并选择重载Bar1 #2。

    【讨论】:

    • 您好,谢谢您的回答!刚刚用一个新问题更新了组织帖子。
    猜你喜欢
    • 1970-01-01
    • 2019-10-11
    • 1970-01-01
    • 1970-01-01
    • 2014-04-20
    • 2014-11-16
    • 2016-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多