3
#include <iostream>


template <typename T>
struct Wrapper {
    operator T const &() const & {
        std::cout << "Wrapper::operator T const &() const &\n";
        return _obj;
    }

    operator T&() & {
        std::cout << "Wrapper::operator T&() &\n";
        return _obj;
    }

    operator T&&() && {
        std::cout << "Wrapper::operator T&&() &&\n";
        return std::move(_obj);
    }

private:
    T _obj;
};

struct Test {
    Test& operator=(Test const &test) {
        std::cout << "Test& Test::operator=(Test const &)\n";
        return *this;
    }

    Test& operator=(Test &&test) {
        std::cout << "Test& Test::operator=(Test &&)\n";
        return *this;
    }
};

int main() {
    Test test;
    Wrapper<Test> wrapperTest;

    test = wrapperTest;               // OK for all
    test = std::move(wrapperTest);    // OK for GCC and ICC, not for Clang and VC++

    return 0;
}

VC++ :

(34): error C2593: 'operator =' is ambiguous

(26): note: could be 'Test &Test::operator =(Test &&)'

(25): note: or 'Test &Test::operator =(const Test &)'

(69): note: while trying to match the argument list '(Test, Wrapper)'

========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Clang :

:34:7: error: use of overloaded operator '=' is ambiguous (with operand types 'Test' and 'typename std::remove_reference &>::type' (aka 'Wrapper'))

test = std::move(wrapperTest); // OK for GCC and ICC, not for Clang and Microsoft Visual C++

~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~

:25:8: note: candidate function

Test& operator=(Test const &test) { std::cout << "Test& Test::operator=(Test const &)\n"; return *this; }

^

:26:8: note: candidate function

Test& operator=(Test &&test) { std::cout << "Test& Test::operator=(Test &&)\n"; return *this; }

^

1 error generated.

5
  • Where is your example for g++? You provided for VC++, but your question's title says g++. Which is it? Visual Studio C++ is a different compiler than GNU g++. Commented Oct 24, 2016 at 15:37
  • I'm confused. They both mention the same issue: operator=() is ambiguous. How is that different? Commented Oct 24, 2016 at 15:39
  • @ThomasMatthews I said that this code compiles fine on GCC but not on Clang and VC++, I put the error message of Clang and VC++. Commented Oct 24, 2016 at 15:49
  • @Deduplicator But with explicit the rvalue conversion will not be called, no ? Commented Oct 24, 2016 at 15:57
  • Wait, what do the & and && function specifiers do? First time I'm seeing them Commented Aug 22, 2018 at 18:22

1 Answer 1

4

I think gcc and icc are correct.

test = std::move(wrapperTest);

assigns a Wrapper<Test>&& to a Test, since no candidate matches for this call, it will consider to operation which takes at most 1 conversion.

From Value categories:

When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload (thus, if both copy and move constructors are available, an rvalue argument invokes the move constructor, and likewise with copy and move assignment operators).

And Non-static member functions:

A non-static member function can be declared with either an lvalue ref-qualifier (the token & after the function name) or rvalue ref-qualifier (the token && after the function name). During overload resolution, non-static cv-qualified member function of class X is treated as a function that takes an implicit parameter of type lvalue reference to cv-qualified X if it has no ref-qualifiers or if it has the lvalue ref-qualifier. Otherwise (if it has rvalue ref-qualifier), it is treated as a function taking an implicit parameter of type rvalue reference to cv-qualified X.

Now, we have these candidates:

  • Test& operator=(Test &&test) by operator T&&() &&
  • Test& operator=(Test const &test) by operator T const &() const &

Based on those paragraphs, compilers should choose Test& operator=(Test &&test) by operator T&&() &&

Sign up to request clarification or add additional context in comments.

5 Comments

Perhaps this answer should reference the Overload resolution page, too?
Why should the compiler choose the && overload? The two implicit conversion sequences are indistinguishable user-defined conversion sequences, because they don't contain the same user-defined conversion function ([over.ics.rank]/3.3). As far as I can tell, the call is indeed ambiguous.
@bogdan But because std::move(wrapperTest) is a rvalue and because of the ref qualifier on conversion operator, it should choose rvalue version, no ?
@Johnmph Both && and const& can bind to an rvalue, so the const& overload of the conversion function is a viable candidate as far as ref qualifiers are concerned. Which of the two is chosen for each of the two reference bindings for the respective parameters of the two assignment operator functions depends on the rules for reference binding ([dcl.init.ref]/5); because of the order of the bullets in that long paragraph, for binding const Test& the const& conversion function overload is preferred, and for Test&& - the && one.
As @bogdan has explained, the overload is ambiguous. Adding explicit in front of your operator T&&() && solves the problem. BTW, I am not surprised that clang picked that up, but I am surprised that VC++ did...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.