8

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

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53
  • 2
    `move(a)` is an rvalue, and `ar` is an lvalue... not the same at all. – Kerrek SB Apr 30 '14 at 00:38
  • @KerrekSB OOPS silly me! Probably need to edit... – iavr Apr 30 '14 at 00:39
  • To avoid spurious constructibility, typically you would disable constructor overloads with `enable_if`. – Kerrek SB Apr 30 '14 at 00:39
  • @KerrekSB No control over target constructors; these conversions are supposed to work with anything, e.g. `std::vector` or `std::string`. – iavr Apr 30 '14 at 00:43
  • @KerrekSB `ar` corrected above. – iavr Apr 30 '14 at 00:44
  • 1
    OK, `B` can be constructed from both `B const &` and from `B &&`... – Kerrek SB Apr 30 '14 at 00:46
  • @KerrekSB Yes, but both `std::move(a)` and `A{}` can only give `B&&`, right? – iavr Apr 30 '14 at 00:48
  • Hm, isn't it just the first template and the question whether T is `B` or `B const &`? Have you tried `B const & x = A{}`? – Kerrek SB Apr 30 '14 at 00:58
  • @KerrekSB I suspected the first template and tried to disable it when `T` is an lvalue reference, but this didn't work. Now `B const & x(A{})` is OK, I guess because a temporary is created, but `B const & x = A{}` is allowed because of `explicit`. And `B const & x{A{}}` is also not possible... (expects an lvalue only?) – iavr Apr 30 '14 at 01:05
  • @KerrekSB Please see my edit 2. – iavr Apr 30 '14 at 01:15
  • 3
    7 and 8 are the same: direct-initialization with rvalue A argument, which is convertible to const B& and to B&& with the same exact-match after the initial user-defined conversion, so that neither copy ctor nor move ctor of B are better than the other. – Cubbi Apr 30 '14 at 01:59
  • would adding a `const&&` overload help? ... not sure what it would do! – Yakk - Adam Nevraumont Apr 30 '14 at 03:04
  • @Yakk I've tried the `const&&` overload, no difference. – iavr Apr 30 '14 at 09:03
  • @Cubbi That's a very simple explanation, thanks! Any chance for a workaround? I tried an additional `operator T` (enabled if `T` is not a reference), but is also ambiguous with `const T&` in the same cases (curiously not with `T&&`). – iavr Apr 30 '14 at 09:14
  • @Cubbi *But wait!* How is an rvalue `A` convertible to `const B&`? It's only supposed to convert to `B&&`, isn't it? I've said the same thing to KerrekSB, 8 comments above :-) – iavr Apr 30 '14 at 09:31
  • 1
    @iavr `const&`-qualified member functions are callable on rvalues. – Cubbi Apr 30 '14 at 10:24
  • @Cubbi That's what I am now realizing, thanks. But then, if you have a "regular" (non-conversion operator) member function, that is also overloaded with `&&`, `&` and `const&` ref-qualifiers, shouldn't a call on an rvalue be ambiguous? How come it is not? What is the difference? – iavr Apr 30 '14 at 10:29
  • I have posted a follow-up: [Generic conversion operator templates and move semantics: any viable solution?](http://stackoverflow.com/q/23389672/2644390). – iavr Apr 30 '14 at 13:51

1 Answers1

6

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.

Cubbi
  • 46,567
  • 13
  • 103
  • 169
  • 1
    Thanks a lot. I can't say I am convinced as to *why* rules should be like that, rather this comes as yet another disappointment from the language. I have made [desperate attempts](http://stackoverflow.com/q/23389672/2644390) to make things work, but for now I have abandoned conversion operators altogether and opted for the more verbose function calls. – iavr Apr 30 '14 at 17:40