VS2013 RC中函数模板的过载解决方案
Overload resolution of function templates in VS2013 RC
在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::DummyType
是T
的非推导上下文。即参数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所需的NULL
到Foo::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。
- 如何巧妙地编写两个函数——一个用于检查是否存在解决方案,另一个用于获取所有解决方案
- 如何通过签名作为模板参数来解决重载函数?
- 函数模板部分专业化-有什么解决方法吗
- 模板重载解决方案:当多个模板匹配时会发生什么?
- sort() 方法 c++ 中的比较器函数.为大量数字获得不同的解决方案
- VS为我提供了对构造函数的另一个解决方案,但我想知道为什么我的工作不起作用
- 模板和unique_ptr继承情况下的重载解决方案
- 在 C++14 中,是否有一种优雅的解决方案可以在可变参数模板中选择可调用和不可调用的类型
- 我可以使用模板作为多态处理数组的安全解决方案
- 在这种情况下,有没有办法用单个解决方案替换两个仅在类型上不同的相似函数?
- 正在(在构造函数中)将其包含一个不良设计的指针传递,如果是的,则解决方案是什么
- 具有模板化类型的动态类型Singleton.这是可行的方法吗?[提供的解决方案]
- Windows上的模板有什么问题?解决方案是什么
- 函数模板部分专用化的解决方法
- 我需要一个非模板解决方案
- 将一个函数模板传递给另一个函数时的重载解决
- 实例化函数的多个模板并在运行时选择的通用解决方案
- VS2013 RC中函数模板的过载解决方案
- 模板函数重载解决方案
- 当模板类未特化时,成员函数的特化模板的解决方案