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

C++ overload function by return type

本文关键字:函数 重载 返回类型 C++      更新时间:2023-10-16

如果我认为我对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;
}

有两个类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)编译ok,并且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.

这清楚地表明,在没有返回值的情况下,Foo(int)确实是正确的重载,并且如果模板无法解析其模板参数中使用的类型,则编译失败是正常的结果。

您没有重载返回类型,而是专门化了一个函数模板,当Foo<B>(int)专门化形成无效类型B::_foo时,该专门化将从SFINAE设置的重载中删除,使Foo<B>(char)函数成为唯一可行的函数。

更详细地说,对Foo<A>(0)的调用首先执行名称查找以查找范围中的所有Foo名称,然后实例化任何函数模板以查找重载候选者,然后重载解析选择最佳匹配。

实例化函数模板的步骤产生以下两个函数声明:

int Foo<A>(int);
A Foo<A>(char);

过载分辨率选择第一个作为最佳匹配。

然而,当调用Foo<B>(0)时,实例化会产生以下声明:

<invalid type>  Foo<B>(int);
B Foo<B>(char);

第一个声明是无效的,所以只有一个重载解析的候选者,所以就是被调用的那个。

Bar示例中,实例化过程中形成的无效类型不在函数声明的"直接上下文"中(它在函数定义中,即主体中),因此SFINAE不适用。

template<class T>
typename T::_foo Foo(int);
template<class T>
typename T Foo(char);

因此,您的代码声明了这个重载函数。太好了。

Foo<A>(0);

在这种情况下,编译器试图为上面声明的原型填写模板,该模板将是:

int Foo(int); 
A foo(char); 

由于您将一个整数作为参数传递,所以第一个参数更匹配,因此编译器使用该参数。

Foo<B>(0);

编译器再次看到这一行,并试图填写原型的模板,但是。。。

WTFDOESNTMAKESENSE?!?!? Foo(int); 
A foo(char); 

很明显,第一个甚至没有意义,所以它放弃了它,使用了第二个重载。这实际上与返回类型无关,而是与在决定你指的是哪个函数之前如何填写模板原型有关。以下是您重新排列的示例以澄清:

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.

这被称为SFINAE,请注意,它只适用于模板参数、返回类型和函数参数中非常的特定情况,但不适用于函数体本身。这就是为什么你的"验证"会导致错误,因为它无法从原型中判断出其中一个函数是无效的,而在决定重载时,原型是唯一考虑的因素。