Array-to-pointer conversion + rvalue-ref: Overload resolution difference GCC vs clang
Asked Answered
D

1

8
#include <iostream>
#define FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }

void foo(char const*&&   ) FUNC() // A
void foo(char const(&)[4]) FUNC() // B

int main()
{
    foo("bar");
}

Demo

When using an rvalue reference in the parameter type of the first overload (A), clang current master unambiguously chooses that overload A over B. GCC current master on the other hand complains about an ambiguity.

I'm quite surprised that the string literal, being an lvalue of 4 char const ([expr.prim.literal]/1, [lex.string]/6) should prefer the array-to-pointer conversion on overload A over the identity conversion on overload B.

Without the rvalue reference, that is void foo(char const*), both GCC and clang reject the call as ambiguous. That's also something I don't fully understand, since I would have guessed that there's still an array-to-pointer conversion and therefore [over.ics.rank]p3.2.1 applies:

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

    • (3.2.1) S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by [over.ics.scs], excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,

What is going on in either case?

Dissipated answered 13/1, 2021 at 15:45 Comment(4)
This issue looks familiar but I have no idea how to search SO for that...Dissipated
Maybe clang looks at eel.is/c++draft/over#ics.rank-3.2.3 and says, well, the decayed string literal is an rvalue...?Dissipated
Without the rvalue reference, that is void foo(char const*), both GCC and clang reject the call as ambiguous. That's also something I don't fully understand CWG1789Gagger
#46116673 relatedGagger
S
3

(This is only a partial answer, covering the second case)

What is going on in either case?

Regarding the second case, as to why the following overloads

void foo(char const*     ) FUNC() // A
void foo(char const(&)[4]) FUNC() // B

yields ambiguous overloads (for both Clang and GCC); [over.ics.rank]/3.2.1 may seem to favour B, being an identity conversion, over A, requiring an array-to-pointer conversion which in turn is in the conversion category of Lvalue Transformation:

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

  • (3.2.1) S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by [over.ics.scs], excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,
  • [...]

However, as I interpret the first emphasized segment above, Lvalue Transformation:s are excluded from both sequences S1 and S2 when applying [over.ics.rank]/3.2.1, and the second emphasized segment applies only after this exclusion has been applied.


As pointed out in a comment by @LanguageLawyer, that the rules indeed allow this ambiguity was highlighted in CWG 1789 which, afaict, have seen no progress or feedback since 2013.

1789. Array reference vs array decay in overload resolution

  • Section: 12.4.4.3 [over.ics.rank]
  • Status: drafting
  • Submitter: Faisal Vali
  • Date: 2013-10-01

The current rules make an example like

template<class T, size_t N> void foo(T (&)[N]);
template<class T> void foo(T *t);

int arr[3]{1, 2, 3};
foo(arr);

ambiguous, even though the first is an identity match and the second requires an lvalue transformation. Is this desirable?

Saddlebow answered 13/1, 2021 at 17:25 Comment(3)
It's not 100% clear to me though what the "excluding" refers to. Does it refer to the detection of a subsequence (= we only look at those parts of the sequence which are no Lvalue Transformation to determine whether or not it's a subsequence) or does it refer to the exclusion of this 3.2.1 point?Dissipated
Though I'm not sure if it matters: If we ignore lvalue transformation, then identity conversion is not a proper subsequence of array-to-pointer conversion -- it's the same conversion sequence.Dissipated
@Dissipated I agree that the 3.2.1 wording regarding excluding is somewhat unclear. I can’t explain why Clang would choose the rvalue overload of your first example, though.Saddlebow

© 2022 - 2024 — McMap. All rights reserved.