VS2013 RC中功能模板的过载分辨率

时间:2013-09-29 21:51:15

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

在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匹配。标准的任何规则?

1 个答案:

答案 0 :(得分: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)prvalue,其求值为零或类型为std::nullptr_t的prvalue。空指针常量可以转换为指针类型;结果是该类型的空指针值,并且可以与对象指针或函数指针类型的每个其他值区分开来。

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

  

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

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

  

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

因此,与省略号转换序列相比,过载#0所需的转换序列类型NULL Foo::DummyType*是更好的转换序列将NULL与重载#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*, ...);

此处,重要的部分是T中的DummyType2<T, ..>。在非推断的上下文中。因此,编译器会尝试从第一个第二个参数中推导出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仅从第一个参数推导出来,并且此重载是首选&amp;选择(出于与后续问题第一次调用相同的原因)。

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

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

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