Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conversion to `const Y` not applicable for `R&&` on clang

The following code compiles fine with g++ (GCC) 4.7.1 20120721, but fails with a recently build clang version 3.2 (trunk).

struct Y {};

struct X {
  operator const Y() const { return Y(); }
};

void f(Y&& y) {}

int main()
{
  f(X());
  return 0;
}

Changing the conversion operator to operator Y() const is sufficient to make the code compile on both compilers.

Which compiler is actually standard compliant in this case? What does the standard actually say about this?

The verbatim error as requested:

bla.cpp:14:5: error: no viable conversion from 'X' to 'Y'
  f(X());
    ^~~
bla.cpp:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'X' to
      'const Y &' for 1st argument
struct Y {
       ^
bla.cpp:1:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'X' to
      'Y &&' for 1st argument
struct Y {
       ^
bla.cpp:6:3: note: candidate function
  operator const Y() const { return Y(); }
  ^
bla.cpp:10:12: note: passing argument to parameter 'y' here
void f(Y&& y) {}
       ^

EDIT: Unfortunately even adding the overload

void f(const Y&) {}

still makes clang choose the rvalue reference overload and so this breaks existing code that used to compile fine, e.g. with standard containers.

like image 784
pmr Avatar asked Oct 08 '12 10:10

pmr


2 Answers

I believe clang is right to reject this. Passing the argument to f(Y&&) requires two conversion steps, the first being your operator const Y() and the second being Y's copy constructor. Both count as user-defined conversions, I think, and both are implicit, which violates the principle that an implicit conversion sequence only includes one user-defined conversion.

This Purpose of returning by const value? contains some interesting insights into the semantics of returning a const T.

Hm, if I try adding an overload void f(const Y&y) as the edited question now does, clang behaves pretty strangly. It still complains about being unable to convert X to Y, and doesn't even list the overload f(const Y& y) in its diagnostic. But once I change the overload to take Y by value, i.e. write void f(const Y y), it complains about the call to f being ambiguous.

This is with XCode 4.5's clang which reports Apple clang version 4.1 (tags/Apple/clang-421.11.66) (based on LLVM 3.1svn). If you can reproduce this with a vanilla clang, you should probably report this on the clang mailing list - sure seems to there's a bug lurking there somewhere...

like image 143
fgp Avatar answered Sep 23 '22 06:09

fgp


The example is ill-formed.

Some N3242 quotes.

8.5.3 paragraph 4:

Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2.

paragraph 5 (bullet labels are mine):

A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows:

  1. If the reference is an lvalue reference and ...
  2. Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e. cv1 shall be const), or the reference shall be an rvalue reference.

    a. If the initializer expression

    • i. is an xvalue, class prvalue, array prvalue or function lvalue and "cv1 T1" is reference-compatible with "cv2 T2", or
    • ii. has a class type (i.e. T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type "cv3 T3", where "cv1 T1" is reference-compatible with "cv3 T3",
    • then the reference is bound to ....

    b. Otherwise, a temporary of type "cv1 T1" is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (8.5). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2. ...

For the function parameter initialization, T1 is Y, T2 is X, and cv1 and cv2 are both empty. 1 is out: the reference is an rvalue reference, not lvalue reference. 2.a.i. is out: X is not reference-compatible with Y. 2.b. is out because copy-initializing an Y from a prvalue of type X involves two user-defined conversions: the conversion function and then the copy constructor of Y. (The exact prohibition is in 13.3.3.1p4.)

For case 2.a.ii., the obvious choice for "cv3 T3" is const Y, but that's no good because Y is not reference-compatible with const Y. You might argue for trying T3 is Y and cv3 is empty, but then you're back to needing the copy constructor of Y as the second implicit user-defined conversion.

like image 21
aschepler Avatar answered Sep 19 '22 06:09

aschepler