7

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

4

2 回答 2

7

您不是在返回类型上重载,而是在特化函数模板,当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 不适用。

于 2013-07-29T17:16:16.837 回答
5
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,请注意,它仅适用于模板参数、返回类型和函数参数的非常特殊的情况,但不适用于函数体本身。这就是您的“验证”导致错误的原因,因为它无法判断原型中的一个函数是无效的,而原型是在重载之间做出决定时唯一考虑的因素。

于 2013-07-29T17:29:28.947 回答