Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move constructor disappears in derived class when adding custom destructor

I have a move-only Base class and a Derived which inherits Base's constructors. I would like to give a Derived a custom destructor, but when I do so it no longer inherits Base's move constructor. Very mysterious. What is happening?

godbolt

// move-only
struct Base {
    Base() = default;
    Base(Base const &) = delete;
    Base(Base &&) {}
};

struct Derived : public Base {
    using Base::Base;

    // remove this and it all works
    ~Derived() { /* ... */ }
};

int main() {
    Base b;
    // works
    Base b2 = std::move(b);

    Derived d;
    // fails
    Derived d2 = std::move(d);
}
like image 643
iPherian Avatar asked Aug 08 '26 05:08

iPherian


1 Answers

The move constructor is not inherited with using Base::Base; in the way that you seem to think it is, because the move constructor in Base does not have the signature that a move constructor in Derived would have. The former takes a Base&&, the latter a Derived&&.

Then in Derived you are declaring a destructor. This inhibits the implicit declaration of a move constructor for Derived. So there is no move constructor in Derived.

The compiler then falls back to Derived's implicitly generated copy constructor for Derived d2 = std::move(d);. But that is defined as deleted because the base class of Derived is not copy-able. (You manually deleted Bases copy constructor.)

In overload resolution the deleted copy constructor is chosen over the Base classes inherited Base(Base&&) constructor (although a Derived rvalue could bind to Base&&), because the latter requires a conversion sequence that is not considered exact match, while binding to a const Derived& is considered exact match for the purpose of overload resolution.

Also there is the proposed wording for the resolution of CWG issue 2356 which would exclude the inherited Base move constructor from participating in overload resolution at all. (From what I can tell this is what the compiler are implementing already.)

If you don't have a good reason to declare a destructor, don't do so. If you do have a reason, then you need to default the move operations again, as you did for the move constructor in Base. (You probably want to default the move assignment operator as well if the classes are supposed to be assignable.)

If you intend to use the class hierarchy polymorphically, you should declare a virtual (defaulted) destructor in the polymorphic base, but you do not need to declare a destructor in the derived classes.

like image 166
walnut Avatar answered Aug 10 '26 20:08

walnut



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!