Tested only in GCC and Clang, the presence of a pass-by-value copy assignment operator in the base class (useful when implementing the copy-and-swap (or copy-and-move) idiom) causes the copy assignment operator in the derived class to be implicitly deleted.

Clang and GCC agree on this; why is this the case?

Example code:

#include <string>
#include <iostream>

struct base {
    base() {
        std::cout << "no-arg constructor\n";
    }
    base(const base& other) :
        str{other.str} {
        std::cout << "copy constructor\n";
    }
    base(base&& other) :
        str{std::move(other.str)} {
        std::cout << "move constructor\n";
    }
    base& operator=(base other) {
        std::cout << "copy assigment\n";
        str = std::move(other.str);
        return *this;
    }
    base& operator=(base&& other) {
        std::cout << "move assigment\n";
        str = std::move(other.str);
        return *this;
    }

    std::string str;
};

struct derived : base {
    derived() = default;
    derived(derived&&) = default;
    derived(const derived&) = default;
    derived& operator=(derived&&) = default;
    derived& operator=(const derived&) = default;
};

derived foo() {
    derived ret;
    ret.str = "Hello, world!";
    return ret;
}

int main(int argc, const char* const* argv) {

    derived a;
    a.str = "Wat";
    a = foo(); // foo() returns a temporary - should call move constructor
    return 0;
}
2

There are 2 best solutions below

0
On BEST ANSWER

In your code, the derived copy assignment is not deleted. What is deleted though is the move assignment, because of [class.copy.assign]/7.4, which states that a defaulted move assignment operator is deleted if overload resolution for the move assignment on a base class is ambiguous.

The compiler wouldn't be able to tell whether to call operator=(base) or operator=(base&&) in order to move the base class.


This is always a problem, even if you try to move assign a base class object directly to another base class object. So it is not really practical to have both overloads. It is not clear to me why you need both. From what I can tell you can remove the operator=(base&&) overload without ill effect.

1
On

[class.copy.assign]/7 A defaulted copy/move assignment operator for class X is defined as deleted if X has:
(7.4) - ...a direct base class M that cannot be copied/moved because overload resolution (16.3), as applied to find M’s corresponding assignment operator, results in an ambiguity...

base's move assignment is ambiguous; it has two assignment operators both accepting an rvalue. Observe that this doesn't compile:

base a, b;
a = std::move(b);

For this reason, move assignment of derived ends up defined as deleted.