C++ template functions priority

4.1k views Asked by At
#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g); // prints "first"

    return 0;
}

To call the second foo overload, the compiler needs to perform only one template type deduction, but for the first overload, it needs to perform two. Can you please explain why the first overload is called?

3

There are 3 answers

1
Barry On BEST ANSWER

Overload resolution is done in multiple steps.

First, through name lookup, we select the list of viable candidates. In this case, that is:

template <class U, class T>
void foo(U&, T&);            // with U = int, T = double

template <class T>
void foo(int&, const T&)     // with T = double

Next, we determine the conversion sequence necessary for each argument for each viable candidate. This is [over.ics.rank]:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if [...]

  • S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,
  • the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,

For the first call, the conversion sequence is (Identity, Identity). For the second call, the conversion sequence is (Identity, Identity). So we're equal there. Neither of those bullet points distinguish the two calls. So we move on.

  • S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

Irrelevant.

  • S1 and S2 are reference bindings (8.5.3) and S1 binds an lvalue reference to a function lvalue and S2 binds an rvalue reference to a function lvalue.

Nope.

  • S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.

Qualification conversion is a pointer thing, nope.

  • S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

In this case, the first overload takes its second argument as double& while the second overload takes a const double&. The former is less cv-qualified than the latter, so we stop here - preferring foo(U&,T&).

Only after the steps to determine which conversion sequence is better do we get to the step that the more specialized template is preferred. The full rule ordering in [over.match.best] is:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

  • for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

That's what we just went through.

  • the context is an initialization by user-defined conversion [ ... ]
  • the context is an initialization by conversion function for direct reference binding [ ... ]
  • F1 is not a function template specialization and F2 is a function template specialization, or, if not that,
  • F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

That's why we pick foo(U&, T&). If you remove the const, however, then both conversion sequences are identical across all steps - so at that point, the more specialized template (foo(int&, T&)) would win.

Note that more specialized is the very last mechanism to determine best candidate. It's the most final of tie breakers.

Also note that the number of template deductions is irrelevant. It may matter in selecting between overload that is a template and an overload that is not a template - but it does not matter in selecting between an overload that has x template parameters and an overload that has y > x template parameters.

0
mavam On

Observe:

  • The first overload takes a non-const lvalue as second argument
  • The second overload take a const lvalue as second argument

Because you're passing g as a non-const lvalue, the compiler chooses the first overload.

0
psliwa On

You declare in the second function that you second argument to be const. The below example of your with applied correction calls the second one:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g);

    return 0;
}

On the other hand, when you explicitly declare second argument do be const in main(), the application calls second function in your above example as expected:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    const double g = 2.;
    foo(a, g);

    return 0;
}