重载解析、模板和继承

Overload resolution, templates and inheritance

#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

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

I was under the impression that a suitable non-template function would always be chosen over a template function.

这仅在模板和非模板是同样好的 候选者时成立。这就是 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()) 应该毫不含糊地选择第一个。这正是选择模板的原因。

n3376 13.3.3.1/6

When the parameter has a class type and the argument expression has a derived class type, the implicit conversion sequence is a derived-to-base Conversion from the derived class to the base class.

n3376 13.3.3.1/8

If no conversions are required to match an argument to a parameter type, the implicit conversion sequence is the standard conversion sequence consisting of the identity conversion (13.3.3.1.1).

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

因此,编译器在第一种情况下只有候选者

// 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&)

只有第一名才有身份等级,才会被选中。

Can someone explain to me the resolution steps that lead to this somewhat surprising result?

您可以查看 cppreference.com 中的 过载解决方案 http://en.cppreference.com/w/cpp/language/overload_resolution

具体参见隐式转换序列排名

部分

答案的扩展:

我试图通过上述 link:

中的信息摘录来提供更多说明

A function template by itself is not a type, or a function, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be determined so that the compiler can generate an actual function (or class, from a class template).

为此,编译器经过:

  • 函数模板名称查找
  • 模板参数推导

到这里为止,编译器有几个候选函数定义可以处理特定的函数调用。这些候选项是模板函数的实例以及程序中的相关非模板函数定义。

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

Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before overload resolution.

在模板函数实例化之后执行函数重载决策这一事实是您的代码输出的原因。

现在您的具体案例通过重载决议如下:

Overload Resolution:

If the [previous] steps produce more than one candidate function, then overload resolution is performed to select the function that will actually be called. In general, the candidate function whose parameters match the arguments most closely is the one that is called. . . .

...
F1 is determined to be a better function than F2 if implicit conversions for all arguments of F1 are not worse than the implicit conversions for all arguments of F2, and
1) there is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2
... .
.
.
Ranking of implicit conversion sequences:

Each type of standard conversion sequence is assigned one of three ranks:
1) Exact match: no conversion required, lvalue-to-rvalue conversion, qualification conversion, user-defined conversion of class type to the same class
2) Promotion: integral promotion, floating-point promotion
3) Conversion: integral conversion, floating-point conversion, floating-integral conversion, pointer conversion, pointer-to-member conversion, boolean conversion, user-defined conversion of a derived class to its base

The rank of the standard conversion sequence is the worst of the ranks of the standard conversions it holds (there may be up to three conversions)

Binding of a reference parameter directly to the argument expression is either Identity or a derived-to-base Conversion:

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&)