重载解析,模板和继承

时间:2015-02-09 08:56:01

标签: c++ templates overload-resolution

#include <iostream>

struct A {};
struct B : public A {};

template<typename T>
void foo(const T &x) { std::cout << "Called template" << std::endl; }

void foo(const A &a) { std::cout << "Called A" << std::endl; }

int main()
{
    foo(A());
    foo(B());
    return 0;
}

打印:

Called A
Called template

我的印象是,总是会在模板函数上选择合适的非模板函数。有人可以向我解释导致这种有些令人惊讶的结果的解决步骤吗?

3 个答案:

答案 0 :(得分:16)

  

我的印象是,总是会在模板函数上选择合适的非模板函数。

仅当模板和非模板是同样优秀的候选者时才会成立。这就是为foo(A())选择非模板的原因。

但是,在foo(B())的情况下,使用非模板需要派生到基础的转换。因此功能模板严格更好,因此它被选中。

foo模板实例化为void foo(const B&)。考虑一下没有模板会是什么样子:

void foo(const B &x) { std::cout << "Called template" << std::endl; }

void foo(const A &a) { std::cout << "Called A" << std::endl; }

我相信你会同意,foo(B())应该毫不含糊地选择第一个。这正是选择模板的原因。

答案 1 :(得分:5)

n3376 13.3.3.1/6

  

当参数具有类类型且参数表达式具有   派生类类型,隐式转换序列是一个   derived-to-base从派生类到基类的转换。

n3376 13.3.3.1/8

  

如果不需要转换来将参数与参数匹配   类型,隐式转换序列是标准转换   由身份转换组成的序列(13.3.3.1.1)。

身份转换在13.3.3.1.1 /表12中具有完全匹配等级到期表,但派生到基数比身份更差。

所以,编译器只有第一种情况的候选人

// template after resolving
void foo(const A&)

// non-template
void foo(const A&)

两者都有身份等级,但由于第一个是功能模板,因此将选择第二个。 在第二种情况下

// template after resolving
void foo(const B&)

// non-template
void foo(const A&)

只有第一个具有身份等级并将被选中。

答案 2 :(得分:2)

  

有人可以向我解释导致这种有些令人惊讶的结果的解决步骤吗?

您可以在cppreference.com上查看重载决议http://en.cppreference.com/w/cpp/language/overload_resolution

特别参见隐式转换序列的排名

部分

答案的延伸:

我试图通过上述链接中的信息摘录提供更多说明:

  

函数模板本身不是类型,函数或任何其他实体。不包含仅包含模板定义的源文件的代码。为了显示任何代码,必须实例化模板:必须确定模板参数,以便编译器可以从类模板生成实际函数(或类)。

为此,编译器会通过:

  • 功能模板名称查找
  • 模板参数演绎

到此为止,编译器有一些候选函数定义,可以处理特定的函数调用。这些候选者是模板函数的 instannces 以及程序中相关的非模板函数定义。

但你问题的答案实际上就在这里:

  

模板参数推导发生在函数模板名称查找之后(可能涉及依赖于参数的查找)和之前重载解析。

在模板函数实例化之后执行函数重载解析的事实是代码输出的原因。

现在,您的具体案例将通过重载解析进行如下操作:

  

重载决议:

     

如果[previous]步骤产生多个候选函数,则执行重载决策以选择实际调用的函数。通常,参数与参数最匹配的候选函数是被调用的候选函数。   。   。   

     

...
  如果F1的所有参数的隐式转换不比F2的所有参数的隐式转换差,则确定F1是比F2更好的函数,并且
     1)F1至少有一个参数,其隐式转换优于F2
的该参数的相应隐式转换   ...   。
  。
  。
  隐式转换序列的排名:

     

每种类型的标准转换序列分配三个等级中的一个:
  1)完全匹配:无需转换,左值到右值转换,限定转换,用户定义的类类型转换为同一类别
  2)推广:整体推广,浮点推广   3)转换:积分转换,浮点转换,浮点积分转换,指针转换,指针到成员转换,布尔转换,用户定义的派生类转换为基础

     

标准转换序列的排名是其持有的标准转化次数中最差的(最多可能有三次转化)

     

直接将参数参数绑定到参数表达式是Identity或派生到基础的转换:

struct Base {};
struct Derived : Base {} d;
int f(Base&);    // overload #1
int f(Derived&); // overload #2
int i = f(d); // d -> Derived& has rank Exact Match
              // d -> Base& has rank Conversion
              // calls f(Derived&)