VS2013 RC中函数模板的过载解决方案

Overload resolution of function templates in VS2013 RC

本文关键字:解决方案 函数模板 RC VS2013      更新时间:2023-10-16

在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:usersdummydocumentsvisual studio 2013projectstesttest.cpp(25): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:usersdummydocumentsvisual studio 2013projectstesttest.cpp(15): could be 'int Bar<Foo>(T *,...)'
1>          with
1>          [
1>              T=Foo
1>          ]
1>          c:usersdummydocumentsvisual studio 2013projectstesttest.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:usersdummydocumentsvisual studio 2013projectstesttest.cpp(28): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:usersdummydocumentsvisual studio 2013projectstesttest.cpp(21): could be 'int Bar<Foo>(...)'
1>          c:usersdummydocumentsvisual studio 2013projectstesttest.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匹配的原因是什么。有标准的规则吗?

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

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.dexpract.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.variable]/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.dexer]/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的定义现在变得有些相关:

[支持类型]/3

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

[conv.ptr]/1

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

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

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

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

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

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

[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<T, ..>中的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。