Consider this simple pair of function templates.
template <typename T>
void foo(T& ) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
template <typename C>
void foo(const C& ) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
If we call foo
with a non-const argument:
int i = 4;
foo(i);
The T&
overload is preferred based on [over.ics.rank]/3.2.6, since the deduced reference int&
is less cv-qualified than the deduced reference const int&
.
However, if we call foo
with a const argument:
const int ci = 42;
foo(ci);
The const C&
overload is preferred because it is "more specialized" based on [over.match.best]/1.7. But what are the rules to determine this? My understanding was that you synthesize a type for C
(call it, M
) and try to perform deduction on foo(M)
- but that would succeed (with T == M
). It's only an rvalue that would cause that deduction to fail - but how does the compiler know that it has to choose an rvalue in the synthesis step?
Disclaimer: The types we consider are always types of parameters. The types/value categories/etc. of the actual arguments passed are solely considered in overload resolution, never in partial ordering.
Partial ordering considers both overloads in two "turns", in which one template is always the parameter template, and the other template is the argument template. [temp.deduct.partial]/2:
For each of the templates involved there is the original function type
and the transformed function type. [..] The deduction
process uses the transformed type as the argument template and the
original type of the other template as the parameter template. This
process is done twice for each type involved in the partial ordering
comparison: once using the transformed template-1 as the argument
template and template-2 as the parameter template and again using the
transformed template-2 as the argument template and template-1 as the
parameter template.
You should be familiar with the way transformed "templates" are generated. This is specified in §14.5.6.2/3.
To produce the transformed template, for each type, non-type, or
template template parameter (including template parameter packs
(14.5.3) thereof) synthesize a unique type, value, or class template
respectively and substitute it for each occurrence of that parameter
in the function type of the template.
So our (transformed) argument templates are
void foo( Unique1& );
void foo( Unique2 const& );
[temp.deduct.partial]/3 & /4:
The types used to determine the ordering depend on the context in
which the partial ordering is done:
- In the context of a
function call, the types used are those function parameter types for
which the function call has arguments. [..]
Each type nominated above from the parameter template and the corresponding type from the argument
template are used as the types of P
and A
.
Thus we have two turns, and in both we have a type P
and a type A
:
Turn 1:
P1
: T const&
A1
: Unique1&
Turn 2:
P2
: T&
A2
: Unique2 const&
But before the fun begins, some transformations are performed on these types as well - I abbreviated [temp.deduct.partial]/5 & /7:
- If
P
or A
are references, then they're replaced by the type they refer to.
- Any top-level cv-qualifiers are removed.
Note that the removed cv-qualifiers are "remembered" for later. [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.
Thus we're left with
Turn 1:
P1
: T
A1
: Unique1
Turn 2:
P2
: T
A2
: Unique2
Now we perform deduction - which clearly succeeds in both turns by setting T=Unique[1/2]
. From [temp.deduct.partial]/8:
If deduction succeeds for a given type, the type from the argument template is considered
to be at least as specialized as the type from the parameter template.
That gives us both that Unique1&
is at least as specialized as T const&
, and that Unique2 const&
is at least as specialized as T&
.
However, this is where [temp.deduct.partial]/(9.2) steps in:
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):
The remembered cv-qualifiers come into play. A2
is "more cv-qualified (as described above)" than P2
, hence P2
is not considered to be at least as specialized as A2
.
Finally, [temp.deduct.partial]/10:
Function template F
is at least as specialized as function template G
if, for each pair of types used to determine the ordering, the type
from F
is at least as specialized as the type from G
.
F
is more specialized
than G
if F
is at least as specialized as G
and G
is not at least as specialized as F
.
implies that since the type T&
is not at least as specialized as Unique2 const&
and we already established that T const&
is at least as specialized as Unique1&
, the T const&
-overload is more specialized than the T&
-overload.
The aforementioned rule in paragraph 9 is currently subject of CWG #2088 created four months ago by R. Smith:
The late tiebreakers for lvalue-vs-rvalue references and
cv-qualification in 14.8.2.4 [temp.deduct.partial] paragraph 9 are
applied
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):
However, this is based on a false assumption. [..] We need to decide whether the rule is “deduction succeeds in both directions” or “the types are identical.” The latter seems more reasonable.
This will not alter the result established though, since the types we got are indeed identical.