C++ Overload Resolution – How to Resolve Overload for Copy Assignment Operator in C++23

assignment-operatorc++c++23explicit-object-parameterlanguage-lawyer

In addition to an implicitly-defined copy assignment operator, if a class also defines an operator= without an lvalue-reference object parameter, which of the operators must be selected?

Please consider the following example containing two structs, each with an overloaded copy assignment operator:

struct A {
    int operator =(this const A &, const A&) { return 1; }
    operator int() const { return 2; }
};

struct B {
    int operator =(this volatile B &, const B&) { return 1; }
    operator int() const { return 2; }
};

template<typename T>
int f(T t) {
    return t = T{};
}

int main() {
    return 10 * f(A{}) + f(B{});
}

The program returns two digits. The first digit is 1 if the user-defined operator = is selected in A a; a = A{}; and it is 2 if the implicitly-defined copy assignment operator is selected. The second digit similarly shows the selection, but for B b; b = B{}; current compilers disagree on the program's returned value.

Clang returns 11, meaning that both user-defined operators are preferred over implicitly-defined copy assignment operators.

MSVC returns 22, always selecting the implicitly-defined copy assignment operators over user-defined operators.

And GCC is in the middle, with the return value 21. Online demo: https://gcc.godbolt.org/z/d4hbe7cn8

Which compiler is correct here?

Best Answer

Clang is correct, the others aren't.

Both of your declared assignment operators are copy assignment operators per [class.copy.assign]/1:

A user-declared copy assignment operator X​::​operator= is a non-static non-template member function of class X with exactly one non-object parameter of type X, X&, const X&, volatile X&, or const volatile X&.

In both of your operator= declarations the first parameter is the explicit object parameter (because of this), i.e. an object parameter, and therefore doesn't count in the determination for copy assignment operator.

As a consequence, the compiler must not declare any copy or move assignment operator implicitly.

There is no implicit overload that could be chosen in overload resolution.

See CWG 2586.