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.
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...
The example is ill-formed.
Some N3242 quotes.
8.5.3 paragraph 4:
Given types “cv1
T1
” and “cv2T2
,” “cv1T1
” is reference-related to “cv2T2
” ifT1
is the same type asT2
, orT1
is a base class ofT2
. “cv1T1
” is reference-compatible with “cv2T2
” ifT1
is reference-related toT2
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 "cv2T2
" as follows:
- If the reference is an lvalue reference and ...
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 "cv2T2
", or- ii. has a class type (i.e.
T2
is a class type), whereT1
is not reference-related toT2
, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type "cv3T3
", where "cv1T1
" is reference-compatible with "cv3T3
",- 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. IfT1
is reference-related toT2
, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With