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

时间:2013-07-29 17:13:38

标签: 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;
}

有两个A和B类.A定义了typedef _foo,B没有定义。函数模板Foo,Foo(int)和Foo(char)有两个重载。 Foo(int)返回T :: _ foo,Foo(char)返回T。

然后调用Foo(0)两次。这与Foo(int)完全匹配,所以我希望Foo&lt; A&gt;(0)编译好,而Foo&lt; B&gt;(0)编译失败,因为B没有定义模板中使用的类型_foo。

实际发生的是Foo&lt; B&gt;(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)确实是正确的重载,并且如果模板无法解析从其模板参数中使用的类型,则编译失败是正常结果。

2 个答案:

答案 0 :(得分: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示例中,在实例化期间形成的无效类型不在&#34;直接上下文&#34;函数声明(函数定义中的函数,即正文),因此SFINAE不适用。

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