简体   繁体   中英

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;
}

This prints:

Called A
Called template

I was under the impression that a suitable non-template function would always be chosen over a template function. Can someone explain to me the resolution steps that lead to this somewhat surprising result?

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

This only holds if the template and the non-template are equally good candidates. That's why the non-template is chosen for foo(A()) .

However, in the case of foo(B()) , using the non-template requires a derived-to-base conversion. So the function template is strictly better, and hence it's chosen.

The foo template instantiates into void foo(const B&) . Consider what it would look like without templates:

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

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

I believe you'll agree calling foo(B()) should unambiguously pick the first one. That's exactly why the template is chosen.

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

Identity conversion has exact match rank due table in 13.3.3.1.1/table 12, but derived-to-base is worse, than identity.

So, compiler just have candidates in first case

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

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

Both has identity rank, but since first is function-template, second will be chosen. And in second case

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

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

Only first has identity rank and will be chosen.

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

you may look at Overload Resolution at cppreference.com: http://en.cppreference.com/w/cpp/language/overload_resolution

in particular see the section Ranking of implicit conversion sequences

Extension of the Answer:

I tried to provide more clarification with an excerpt of the information from the aforementioned 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).

For that, the compiler goes through:

  • function template name lookup
  • template argument deduction

Down to here, the compiler has a couple of candidate function definitions which can handle the specific function call. These candidates are instannces of the template function as well as relevant non-template function definitions in the program.

But the answer to your question lies in fact here:

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

The fact that function overload resolution is performed after template function instantiation is the reason for the ouput of your code.

Now your specific case goes through overload resolution as the following:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM