Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this overload of a conversion operator chosen?

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?

like image 319
avakar Avatar asked Dec 02 '19 16:12

avakar


2 Answers

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 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; ...

like image 172
Brian Bi Avatar answered Sep 20 '22 07:09

Brian Bi


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.

like image 35
Yakk - Adam Nevraumont Avatar answered Sep 23 '22 07:09

Yakk - Adam Nevraumont