Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is an lvalue-ref overload unambiguously chosen over a forwarding-ref overload for an lvalue?

Take a look at these two overloaded function templates:

template <class T>
int foo(T& x) {  // #1
  return 1;
}
template <class T>
int foo(T&& x) {  // #2
  return 2;
}

I call foo in the following way:

int i;
foo(i);  // calls #1

And overload #1 is unambiguously chosen: https://gcc.godbolt.org/z/zchK1zxMW

This might seem like a proper behavior, but I cannot understand why it happens (and I actually had code where this was not what I expected).

From Overload resolution:

If any candidate is a function template, its specializations are generated using template argument deduction, and such specializations are treated just like non-template functions except where specified otherwise in the tie-breaker rules.

OK, let's generate specializations. For #1 it's easy, it becomes int foo(int& x). For #2 special deduction rules apply, since it is a forwarding reference. i is an lvalue, thus T is deduced as int& and T&& becomes int& &&, which after reference collapsing becomes just int&, yielding the result int foo(int& x). Which is exactly the same as for #1!

So I'd expect to see an ambiguous call, which does not happen. Could anyone please explain why? What tie-breaker is used to pick the best viable function here?

See also the related discussion in Slack, which has some thoughts.

like image 583
Mikhail Avatar asked Oct 08 '21 20:10

Mikhail


1 Answers

The non language lawyer answer is that there is a tie breaker rule for exactly this case.

Understanding standard wording well enough to decode it would require a short book chapter. But when deduced T&& vs T& overloads are options being chosen between for an lvalue and everything else ties, the T& wins.

This was done intentionally to (a) make universal references work, while (b) allowing you to overload on lvalue references if you want to handle them seperately.

The tie breaker comes from the template function overload "more specialized" ordering rules. The same reason why T* is preferred over T for pointers, even though both T=Foo* and T=Foo give the same function parameters. A secondary ordering on template parameters occurs, and the fact that T can emulate T* means T* is more specialized (or rather not not, the wording in the standard is awkward). An extra rule stating that T& beats T&& for lvalues is in the same section.

like image 149
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 11:10

Yakk - Adam Nevraumont