Search code examples
c++c++17deleted-functionscopy-and-swapdefaulted-functions

Why does copy-and-swap in a base class cause the copy-assignment operator to be implicitly deleted in the derived class?


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;
}

Solution

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