Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded operator ambiguity on Clang but not on GCC, which one is correct?

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

like image 507
Johnmph Avatar asked Nov 16 '25 11:11

Johnmph


1 Answers

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&&() &&

like image 145
Danh Avatar answered Nov 19 '25 03:11

Danh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!