Home » C++ » Why is derived class move constructible when base class isn't?

Why is derived class move constructible when base class isn't?

Posted by: admin November 30, 2017 Leave a comment

Questions:

Consider the following example:

#include <iostream>
#include <string>
#include <utility>

template <typename Base> struct Foo : public Base {
    using Base::Base;
};

struct Bar {
    Bar(const Bar&) { }
    Bar(Bar&&) = delete;
};

int main() {
    std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
    std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}

Why does the compiler generate a move constructor despite the base class being non-move-constructible?

Is that in the standard or is it a compiler bug? Is it possible to “perfectly propagate” move construction from base to derived class?

Answers:

Because:

A defaulted move constructor that is defined as deleted is ignored by overload resolution.

([class.copy]/11)

Bar‘s move constructor is explicitly deleted, so Bar cannot be moved. But Foo<Bar>‘s move constructor is implicitly deleted after being implicitly declared as defaulted, due to the fact that the Bar member cannot be moved. Therefore Foo<Bar> can be moved using its copy constructor.

Edit: I also forgot to mention the important fact that an inheriting constructor declaration such as using Base::Base does not inherit default, copy, or move constructors, so that’s why Foo<Bar> doesn’t have an explicitly deleted move constructor inherited from Bar.

Questions:
Answers:

1. The behavior of std::is_move_constructible

This is expected behavior of std::is_move_constructible:

Types without a move constructor, but with a copy constructor that accepts const T& arguments, satisfy std::is_move_constructible.

Which means with a copy constructor it’s still possible to construct T from rvalue reference T&&. And Foo<Bar> has an Implicitly-declared copy constructor.

2. The implicitly-declared move constructor of Foo<Bar>

Why does compiler generates move constructor despite base class being non-move-constructible?

In fact, the move constructor of Foo<Bar> is defined as deleted, but note that the deleted implicitly-declared move constructor is ignored by overload resolution.

The implicitly-declared or defaulted move constructor for class T is
defined as deleted in any of the following is true:

...
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); 
...

The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).

3. The different behavior between Bar and Foo<Bar>

Note that the move constructor of Bar is declared as deleted explicitly, and the move constructor of Foo<Bar> is implicitly-declared and defined as deleted. The point is that the deleted implicitly-declared move constructor is ignored by overload resolution, which makes it possible to move construct Foo<Bar> with its copy constructor. But the explicitly deleted move constructor will participate in overload resolution, means when trying to move constructor Bar the deleted move constructor will be selected, then the program is ill-formed.

That’s why Foo<Bar> is move constructible but Bar is not.

The standard has an explicit statement about this. $12.8/11 Copying and moving class objects
[class.copy]

A defaulted move constructor that is defined as deleted is ignored by overload resolution ([over.match], [over.over]). [ Note: A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead. — end note ]