Consider the following code.
struct any { template <typename T> operator T &&() const; template <typename T> operator T &() const; }; int main() { int a = any{}; }
Here the second conversion operator is chosen by the overload resolution. Why?
As far as I understand it, the two operators are deduced to operator int &&() const
and operator int &() const
respectively. Both are in the set of viable functions. Reading through [over.match.best] didn't help me figure out why the latter is better.
Why is the latter function better than the former?
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; ...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With