c++language-lawyerassignment-operatorc++23explicit-object-parameter

Overload resolution for copy assignment operator


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?


Solution

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