14

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.

Boann
  • 48,794
  • 16
  • 117
  • 146
Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • I wound up deleting my answer because I'm having a real hard time to to explain the section of the standard that governs this. Pretty much paragraphs 2 through 4 [here](https://timsong-cpp.github.io/cppwp/temp.func.order) cover this – NathanOliver Oct 08 '21 at 20:45
  • 1
    I think the point here is that the "except where specified otherwise in the tie-breaker rules" includes "where it says that more-specialized templates are preferred over less-specialized", and #1 only accepting lvalue references is more specialized than #2 accepting a forwarding reference. – Nathan Pierson Oct 08 '21 at 21:02
  • 1
    The process is "partial ordering of functions templates". It's covered by [this](https://timsong-cpp.github.io/cppwp/n4868/temp.func.order) section and [this](https://timsong-cpp.github.io/cppwp/n4868/temp.deduct.partial) section. Both are quite the read for figuring out the algorithm. But there's a [decent post](https://stackoverflow.com/q/17005985/817643) going thorough the process that may help clarify how it works. – StoryTeller - Unslander Monica Oct 08 '21 at 21:20
  • 3
    As for *this* case. If one was to perform the ordering here, they'll eventually have to apply [this bullet](https://timsong-cpp.github.io/cppwp/n4868/temp.deduct.partial#9.1). Which basically gives lvalue references a slight advantage in the rank, and thus plays a key part in choosing the lvalue overload. – StoryTeller - Unslander Monica Oct 08 '21 at 21:26

1 Answers1

4

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524