0

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

4

1 回答 1

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

替换失败:

对于第二个 call Bar((int*)NULL);,替换Tforint会导致第一个重载 [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

now的定义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

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

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

[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*, ...);

在这里,重要的部分是Tin 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仅从第一个参数推导出来,并且此重载是首选和选择的(出于与后续问题的第一次调用相同的原因)。

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

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

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

于 2013-09-29T22:20:17.153 回答