Given the following conversion operators
struct A
{
template<typename T> explicit operator T&& () &&;
template<typename T> explicit operator T& () &;
template<typename T> explicit operator const T& () const&;
};
struct B {};
I would expect the following conversions to be all valid, yet some give compile errors (live example):
A a;
A&& ar = std::move(a);
A& al = a;
const A& ac = a;
B&& bm(std::move(a)); // 1. OK
B&& bt(A{}); // 2. OK
B&& br(ar); // 3. error: no viable conversion from A to B
B& bl(al); // 4. OK
const B& bz(al); // 5. OK
const B& bc(ac); // 6. OK
B cm(std::move(a)); // 7. error: call to constructor of B ambiguous
B ct(A{}); // 8. error: call to constructor of B ambiguous
B cr(ar); // 9. OK
In particular, 1 appears to be identical to 3, and almost identical to 2 (similarly for 7 to 9, 8), yet behave differently.
Any explanation or workaround?
My motivation is Yet another 'any', where I eventually had to make all conversion operators explicit
to avoid problems with type traits like std::is_constructible
, std::is_convertible
, then I bumped into new problems.
EDIT Sorry, please ignore 3 and 9, my mistake (thanks Kerrek SB). Yet 7 and 8 remain as problems. Also, explicit
appears to be irrelevant after all, sorry again.
EDIT 2 Just noticed that
B cm = std::move(a);
B ct = A{};
are valid if the conversion operators are not explicit
. So that's where explicit
comes in: initially my samples used copy-initialization, and when I switched to explicit
I had to use direct-initialization. Then this problem came up (cases 7 and 8).
Yet 7 and 8 remain as problems
B cm(std::move(a)); // 7. error: call to constructor of B ambiguous
B ct(A{}); // 8. error: call to constructor of B ambiguous
The two cases are the same: direct initialization with rvalue argument of type A.
The candidate functions for direct initialization are all constructors, and in this case, both copy constructor B::B(const B&)
and move constructor B(B&&)
are viable, since there is an implicit conversion from rvalue A to both const B&
and to B&&
. Overload resolution cannot decide between these two constructors because calling either one requires a user-defined conversion directly into the parameter type, and user-defined conversion sequences are only ranked by the second standard conversion:
13.3.3.2/3[over.ics.rank]
: User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function ... and the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2."
This is different from calling a member function that has both && and const &-qualified overloads because in that case, overload resolution is ranking the reference bindings from rvalue argument to implict object parameter accoring to
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.