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;
}
olist
  • 877
  • 5
  • 13
  • what do you mean with "better" ? – 463035818_is_not_an_ai Sep 08 '17 at 12:11
  • 2
    Fwiw, this [succeeds with clang](https://godbolt.org/g/gi1C9n) – WhozCraig Sep 08 '17 at 12:13
  • 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=`. But you can't. – Sam Varshavchik Sep 08 '17 at 12:24
  • TIL that the pointer created from array to pointer decay is a prvalue (I guess it's unsurprising in retrospect) – AndyG Sep 08 '17 at 12:24
  • @WhozCraig: [MSVC calls #1](http://rextester.com/PEFTHV54742) 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? – AndyG Sep 08 '17 at 12:29
  • @AndyG I would have thought so as well. Interesting problem – WhozCraig Sep 08 '17 at 12:34
  • IMHO #1 should be preferred as no array decay needs to take place. This is very interesting. – NathanOliver Sep 08 '17 at 12:37
  • 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. – AndyG Sep 08 '17 at 12:48
  • 1
    @SamVarshavchik why, of course it is. 8.3.2 \[dcl.ref] "A reference type that is declared using `&` is called an lvalue reference". – n. m. could be an AI Sep 08 '17 at 14:07
  • @AndyG Reference initialization is identity conversion, but identity conversion doesn't "outrank" exact match, see 13.3.3.1.1, Table 11. – Nir Friedman Sep 08 '17 at 14:08
  • @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) – AndyG Sep 08 '17 at 14:11
  • int arr[6]; is an lvalue right? – Zebrafish Sep 08 '17 at 14:33
  • @NathanOliver Array decay is an lvalue transformation, so it doesn't matter (see, e.g., [this](https://stackoverflow.com/q/28243371/2069064)) – Barry Sep 08 '17 at 18:14
  • @Barry Cool. Thanks for the info. – NathanOliver Sep 08 '17 at 18:20

1 Answers1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 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? – W.F. Sep 08 '17 at 18:55
  • +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. – AndyG Sep 08 '17 at 20:02
  • 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. – T.C. Sep 08 '17 at 21:03
  • @T.C. 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? – Barry Sep 08 '17 at 21:09