Why is this overload of a conversion operator chosen?
The deduced return value conversion operators are a bit strange. But the core idea is that it acts like a function parameter to pick which one is used.
And when deciding between T&&
and T&
the T&
wins in the overload resolution rules. This is to permit:
template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }
to work. T&&
can match against an lvalue, but when both the lvalue and universal reference overloads are available, the lvalue one is preferred.
The right set of conversion operators is probably:
template <typename T>
operator T&&() &&;
template <typename T>
operator T &() const; // maybe &
or even
template <typename T>
operator T() &&;
template <typename T>
operator T &() const; // maybe &
to prevent failed lifetime extension from biting you.
3 The types used to determine the ordering depend on the context in which the partial ordering is done:
[SNIP]
(3.2) In the context of a call to a conversion function, the return types of the conversion function templates are used.
Which then ends up depending on "more specialized" rules when picking overloads:
(9.1) 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,
Thus operator T&&
is not at least as specialized as operator T&
, meanwhile no rule states operator T&
is not at least as specialized as operator T&&
, so operator T&
is more specialized than operator T&&
.
More specialized templates win overload resolution over less, everything else being equal.
The conversion operator that returns T&
is preferred because it is more specialized than the conversion operator that returns T&&
.
See C++17 [temp.deduct.partial]/(3.2):
In the context of a call to a conversion function, the return types of the conversion function templates are used.
and /9:
If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both
P
andA
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; ...
We're trying to initialize an int
from an any
. The process for that it:
Figure out all the way we can do that. That is, determine all of our candidates. These come from the non-explicit conversion functions that can be converted to
int
via a standard conversion sequence ([over.match.conv]). The section contains this phrase:A call to a conversion function returning “reference to
X
” is a glvalue of typeX
, and such a conversion function is therefore considered to yieldX
for this process of selecting candidate functions.Pick the best candidate.
After step 1, we have two candidates. operator int&() const
and operator int&&() const
, both of which are considered to yield int
for the purposes of selecting candidate functions. Which is the best candidate yielding int
?
We do have a tiebreaker that prefers lvalue references to rvalue references ([over.ics.rank]/3.2.3). We're not really binding a reference here though, and the example there is somewhat inverted - this is for the case where the parameter is an lvalue vs rvalue reference.
If that doesn't apply, then we fall through to the [over.match.best]/2.5 tiebreaker of preferring the more specialized function template.
Generally speaking, the rule of thumb is the more specific conversion is the best match. The lvalue reference conversion function is more specific than the forwarding reference conversion function, so it's preferred. There's nothing about the int
we're initializing that requires an rvalue (had we instead been initializing an int&&
, then the operator T&() const
would not have been a candidate).