Question

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).

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top