如果我认为我对C++一无所知,那就是你不能通过返回类型重载函数.
那么有人能解释一下这里发生了什么吗?
class A { public: typedef int _foo; };
class B {};
template<class T>
typename T::_foo Foo(int)
{
cout << "Foo(int)\n"; return typename T::_foo();
}
template<class T>
typename T Foo(char)
{
cout << "Foo(char)\n"; return typename T();
}
int main()
{
Foo<A>(0); // Writes "Foo(int)", as expected.
Foo<B>(0); // Writes "Foo(char), expected error unable to compile template.
return 0;
}
Run Code Online (Sandbox Code Playgroud)
有两个类A和B. A定义typedef _foo,B不定义.函数模板Foo,Foo(int)和Foo(char)有两个重载.Foo(int)返回T :: _ foo,Foo(char)返回T.
然后调用Foo(0)两次.这与Foo(int)完全匹配,所以我希望Foo <A>(0)编译好,而Foo <B>(0)编译失败,因为B没有定义模板中使用的类型_foo.
实际发生的是Foo <B>(0)完全忽略Foo(int)并实例化Foo(char).但是通过正常的重载决策规则,Foo(0)显然是Foo(int)的精确匹配,并且使Foo(char)成为更可行的匹配的唯一因素是不应该考虑的返回类型.
要验证它是影响重载决策的返回值,只需添加:
template<class T>
void Bar(int) { typename T::_foo a; cout << "Bar(int)\n"; }
template<class T>
void Bar(char) { cout << "Bar(char)\n"; }
Bar<A>(0); // Writes "Bar(int), as expected.
//Bar<B>(0); // Error C2039: '_foo' : is not a member of 'B', as expected.
Run Code Online (Sandbox Code Playgroud)
这清楚地表明,在没有返回值的情况下,Foo(int)确实是正确的重载,并且如果模板无法解析从其模板参数中使用的类型,则编译失败是正常结果.
你没有在返回类型上重载,你正在专门化一个函数模板,当Foo<B>(int)专门化形成无效类型时B::_foo,从SFINAE设置的重载中删除了特化,使该Foo<B>(char)函数成为唯一可行的函数.
更详细地说,调用Foo<A>(0)首先执行名称查找以查找Foo范围内的所有名称,然后实例化任何函数模板以查找重载候选项,然后重载决策选择最佳匹配.
实例化函数模板的步骤产生以下两个函数声明:
int Foo<A>(int);
A Foo<A>(char);
Run Code Online (Sandbox Code Playgroud)
过载分辨率选择第一个作为最佳匹配.
但是,在调用Foo<B>(0)实例化时会生成以下声明:
<invalid type> Foo<B>(int);
B Foo<B>(char);
Run Code Online (Sandbox Code Playgroud)
第一个声明无效,因此只有一个候选者可以进行重载解析,因此这是一个被调用的声明.
在您的Bar示例中,在实例化期间形成的无效类型不在函数声明的"直接上下文"中(它在函数定义中,即正文中),因此SFINAE不适用.
template<class T>
typename T::_foo Foo(int);
template<class T>
typename T Foo(char);
Run Code Online (Sandbox Code Playgroud)
所以你的代码声明了这个重载的函数.真好.
Foo<A>(0);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,编译器会尝试为上面声明的原型填写模板,这将是:
int Foo(int);
A foo(char);
Run Code Online (Sandbox Code Playgroud)
因为你传递一个整数作为参数,第一个是更好的匹配,所以编译器使用那个.
Foo<B>(0);
Run Code Online (Sandbox Code Playgroud)
编译器再次看到这一行,并尝试填写原型的模板,但......
WTFDOESNTMAKESENSE?!?!? Foo(int);
A foo(char);
Run Code Online (Sandbox Code Playgroud)
很明显,第一个甚至没有意义,所以它丢弃并使用第二个重载.这实际上与返回类型无关,它与模板原型在决定您指的是哪个函数之前的填充方式有关.这是重新安排的示例,以澄清:
template<class T>
int foo(T::_foo) {}
template<class T>
int foo(char) {}
int main() {
foo<A>(0); //uses the first, `int foo(int)` better than `int foo(char)`
foo<B>(0); //uses the second, because the first doesn't work with B.
Run Code Online (Sandbox Code Playgroud)
这称为SFINAE,请注意它仅适用于模板参数,返回类型和函数参数的非常特殊情况,但不适用于函数体本身.这就是为什么你的"验证"导致错误的原因,因为它无法判断原型中的一个函数是无效的,并且原型是在重载决定时唯一考虑的因素.