Search code examples
c++language-lawyertemplate-argument-deductionfunction-templatespartial-ordering

How to understand the rules of partial ordering about T& and T const&


template <typename T>
void show(T&);       // #1
template <typename T>
void show(T const&); // #2

int main()
{
    int a = 0;
    show(a);        // #1 to be called
}

I'm confused about these partial ordering rules. Here are some quotes: [temp.deduct.partial]/5

Before the partial ordering is done, certain transformations are performed on the types used for partial ordering:

  • If P is a reference type, P is replaced by the type referred to.

  • If A is a reference type, A is replaced by the type referred to.

[temp.deduct.partial]/6

If both P and A were reference types (before being replaced with the type referred to above), determine which of the two types (if any) is more cv-qualified than the other; otherwise the types are considered to be equally cv-qualified for partial ordering purposes. The result of this determination will be used below.

[temp.deduct.partial]/7

Remove any top-level cv-qualifiers:

  • If P is a cv-qualified type, P is replaced by the cv-unqualified version of P.

  • If A is a cv-qualified type, A is replaced by the cv-unqualified version of A.

Firstly, both void show(T&) and void show(T const&) could be called by passing an int lvalue, so we need to use partial order rules to decide which function is more matched. Then, according to the above quotes, we do some transformations. Step 1:

T&       => T          #3
T const& => T const    #4

Step 2:

T       => T    #5
T const => T    #6

#5 => #6, #6 => #5, deduction succeeds in both directions. Then the following rules work: [temp.deduct.partial]/9

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):

  • if the type from the argument template was an lvalue reference and the type from the parameter template was not, the parameter type is not considered to be at least as specialized as the argument type;

  • otherwise, if the type from the argument template is more cv-qualified than the type from the parameter template (as described above), the parameter type is not considered to be at least as specialized as the argument type.

So #4 is more specialized then #3. For a given value a, #2 function should be called, but actually #1 function is called. Why? Is there something wrong with my understanding?


Solution

  • In this case, the "more specialized" rule is not used, which is a tiebreaker in case ranking implicit conversion sequences does not determine the order of the functions: [over.match.best]/1

    Define ICSi(F) as follows:

    [...]

    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

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

    • [...]

    • (1.7) 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 [temp.func.order], or, if not that,

    • [...]

    In this case, ranking implicit conversion sequences alone is enough to determine the ordering, so everything following the "or, if not that" is ignored. Deduction results in (int&) for #1 and (int const&) for #2, so in both cases, reference binding (a to int& and a to int const&) results in the Identity Conversion, regardless of the cv-qualification: [over.ics.ref]/1

    When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion ([over.best.ics]). [...]

    However, ICS1(#1) is a better conversion sequence than ICS1(#2) because of [over.ics.rank]/3:

    Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:

    • [...]

    • (3.2) Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

      • [...]

      • (3.2.6) S1 and S2 are reference bindings ([dcl.init.ref]), 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.

    • [...]

    Therefore, ICS1(F1) is a better conversion sequence than ICS1(F2), thus F1 is better than F2 according to [over.match.best]/(1.3) (above). The [over.match.best]/(1.7) rule is not used.


    The "more specialized" rule is used in the reverse situation:

    int const a = 0;
    show(a);         // #2 should be called for a const lvalue
    

    This time, deduction results in int const& and int const&, so [over.match.best]/(1.7) kicks in. The result, as you observed, is that #2 is a better function than #1.


    (Emphasis mine, for all quotes)