overload resolution between lvalue reference and rvalue reference
Asked Answered
R

1

16
#include <iostream>

using namespace std;

void func(int (&ref)[6]) { cout << "#1" << endl; }
void func(int * &&ref) { cout << "#2" << endl; }

int main()
{
  int arr[6];
  func(arr); // g++(5.4): ambiguous, clang++(3.8): #2, vc++(19.11): #1

  return 0;
}

Both functions are exact matches. Below is a quote from the standard:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

...

S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

Doesn't it imply that the second is better?

Updated:

There is a related question. And the following code is a simplified version of it.

#include <iostream>

using namespace std;

void func(int *&) { cout << "#1" << endl; }
void func(int *&&) { cout << "#2" << endl; }

int main()
{
  int arr[6];
  func(arr);  // g++(5.4) and clang++(3.8): #2, vc++(19.11): ambiguous

  return 0;
}
Reich answered 8/9, 2017 at 12:6 Comment(14)
what do you mean with "better" ?Kat
Fwiw, this succeeds with clangGooseflesh
This turns on whether int (&ref)[6] is an "lvalue reference", as referenced in the quote. Even though this is a reference, it can be argued that this is not a reference to an lvalue. If it were, you could assign to ref, i.e. ref=<something>. But you can't.Dufrene
TIL that the pointer created from array to pointer decay is a prvalue (I guess it's unsurprising in retrospect)Forster
@WhozCraig: MSVC calls #1 so now we're in the rare situation where all 3 major compilers disagree. MSVC calls #1, clang calls #2, and gcc says they're ambiguous. I would have thought #1 was the best choice because it's an exact match, no?Forster
@Forster I would have thought so as well. Interesting problemGooseflesh
IMHO #1 should be preferred as no array decay needs to take place. This is very interesting.Barracuda
Array to pointer conversion falls under "exact match", so the question is whether reference initialization is an "Identity" or also an "exact match" I fear the standard may be ambiguous on this, hence the different behavior amongst all the compilers.Forster
@SamVarshavchik why, of course it is. 8.3.2 [dcl.ref] "A reference type that is declared using & is called an lvalue reference".Mightily
@Forster Reference initialization is identity conversion, but identity conversion doesn't "outrank" exact match, see 13.3.3.1.1, Table 11.Ebert
@NirFriedman: You're right, I missed that. Also depending on which paper you're referencing the table # changes (C++11 it's Table 12, C++14 it's Table 11, and C++17 it's Table 13)Forster
int arr[6]; is an lvalue right?Discarnate
@Barracuda Array decay is an lvalue transformation, so it doesn't matter (see, e.g., this)Gerladina
@Gerladina Cool. Thanks for the info.Barracuda
G
3

I think it depends on what a particular phrase means.

Both conversions are equivalent because we exclude lvalue transformations (basically, an array effectively is a pointer so it doesn't count as a conversion), so we get into the next tiebreaker that you pointed out in [over.ics.rank]:

S1 and S2 are reference bindings and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference

Does this case apply? We do have two reference bindings:

int arr[6];
int (&a)[6] = arr;  // #1
int *&& b = arr;    // #2

Here, #1 binds an lvalue reference. #2 falls into [dcl.init.ref]:

Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.

arr is implicitly converted to a prvalue of type int*, which is then bound to b.


So now the question is - what does the restriction in [over.ics.rank] mean? It could mean:

  • an rvalue reference that is, in general, bound to an rvalue. This is apparently clang's interpretation. The rvalue reference is bound to the temporary materialized from the prvalue conversion of arr.
  • specifically, the argument expression is an rvalue that is bound to the rvalue reference parameter. This is apprently gcc's interpretation, and since arr is not an rvalue (it is an lvalue), this tiebreaker is skipped and no subsequent tiebreakers apply.

I am inclined to favor gcc's implementation here. Otherwise, what would the point of the phrase "binds an rvalue reference to an rvalue" be? Rvalue references cannot bind to lvalues. It's redundant. That said, it's awkwardly worded for that interpretation too.

As is, I'll call it a wording bug.

Gerladina answered 8/9, 2017 at 18:26 Comment(4)
For compilers there are appropriate web pages to share frustrations if something is not working according to standard... Is there a way to file a report in case of a standard defect?Scotty
+1, also inclined to favor gcc's implementation. I'd like to add that the code examples provided underneath [over.ics.rank] strictly deal with binding rvalues to rvalue references, and the only lvalue is bound to an lvalue reference.Forster
Hmm, I'd read this as "S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference [to an rvalue]" with the repetition omitted for brevity.Hypogeous
@Hypogeous I never even thought about that wording. That does make more sense. I think it's worth suggesting an editorial edit to change it to "S1 binds an rvalue reference and S2 binds an lvalue reference to an rvalue"? Also, then how does clang call #2?Gerladina

© 2022 - 2024 — McMap. All rights reserved.