What is the rationale behind the different treatment of implicitly and explicitly deleted move constructors in the C++11 standard, with respect to the implicit generation of move constructors of containing/inheriting classes?
Do C++14/C++17 change anything? (Except DR1402 in C++14)
Note: I understand what is happening, I understand that it is according to the C++11 standard's rules, I'm interested in the rationale for these rules that imply this behavior (please make sure not to simply restate that it is the way it is because the standard says so).
Assume a class ExplicitDelete
with an explicitly deleted move ctor and an explicitly defaulted copy ctor. This class isn't move constructible
even though a compatible copy ctor is available, because overload resolution chooses the move constructor and fails at compile time due to its deletion.
Assume a class ImplicitDelete
which either contains or inherits from ExplicitDelete
and does nothing else. This class will have its move ctor implicitly declared as deleted due to C++11 move ctor rules. However, this class will still be move constructible
via its copy ctor. (Does this last statement have to do with resolution of DR1402?)
Then a class Implicit
containing/inheriting from ImplicitDelete
will have a perfectly fine implicit move constructor generated, that calls ImplicitDelete
's copy ctor.
So what is the rationale behind allowing Implicit
to be able to move implicitly and ImplicitDelete
not to be able to move implicitly?
In practice, if Implicit
and ImplicitDelete
have some heavy-duty movable members (think vector<string>
), I see no reason that Implicit
should be vastly superior to ImplicitDelete
in move performance. ImplicitDelete
could still copy ExplicitDelete
from its implicit move ctor—just like Implicit
does with ImplicitDelete
.
To me, this behavior seems inconsistent. I'd find it more consistent if either of these two things happened:
The compiler treats both the implicitly and explicitly deleted move ctors the same:
ImplicitDelete
becomes notmove-constructible
, just likeExplicitDelete
ImplicitDelete
's deleted move ctor leads to a deleted implicit move ctor inImplicit
(in the same way thatExplicitDelete
does that toImplicitDelete
)Implicit
becomes notmove-constructible
- Compilation of the
std::move
line utterly fails in my code sample
Or, the compiler falls back to copy ctor also for
ExplicitDelete
:ExplicitDelete
's copy constructor is called in allmove
s, just like forImplicitDelete
ImplicitDelete
gets a proper implicit move ctor- (
Implicit
is unchanged in this scenario) - The output of the code sample indicates that the
Explicit
member is always moved.
Here's the fully working example:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class's move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copying
"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving
"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete's copy and 1x Implicit's move
return 0;
}
See Question&Answers more detail:os