C++重载函数按返回类型

Neu*_*ino 6 c++

如果我认为我对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)确实是正确的重载,并且如果模板无法解析从其模板参数中使用的类型,则编译失败是正常结果.

Jon*_*ely 7

你没有在返回类型上重载,你正在专门化一个函数模板,当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不适用.

  • @Neutrino因为SFINAE仅适用于函数的签名. (4认同)
  • 他没有提供函数模板的不同特化,他实际上正在重载(尽管不在返回类型上) (3认同)

Moo*_*uck 5

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,请注意它仅适用于模板参数,返回类型和函数参数的非常特殊情况,但不适用于函数体本身.这就是为什么你的"验证"导致错误的原因,因为它无法判断原型中的一个函数是无效的,并且原型是在重载决定时唯一考虑的因素.