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?
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.
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 ]
Related
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);
}
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.
Move constructors are generated under specific circumstances.
https://en.wikipedia.org/wiki/Special_member_functions
In creating a destructor, you have stopped the generation of a move constructor by the compiler.
Also, create a virtual Base destructor if you don't have one. Default it if it doesn't have to do anything special. Same with your Base move constructor, just don't leave it empty, declare it default. You're using =delete, use =default as well.
The inherited move constructor does not have the signature for the derived class.
In the first case without the explicitly declared destructor the compiler implicitly declares the default move constructor for the derived class.
In the second case when the destructor is explicitly declared the move constructor is not implicitly declared by the compiler.
From the C++ 17 Standard (15.8.1 Copy/move constructors)
8 If the definition of a class X does not explicitly declare a move
constructor, a non-explicit one will be implicitly declared as
defaulted if and only if
(8.1) X does not have a user-declared copy constructor,
(8.2) X does not have a user-declared copy assignment operator,
—(8.3) X does not have a user-declared move assignment operator, and
> —(8.4) X does not have a user-declared destructor.
But in any case the ,move constructor of the base class is not the move constructor of the derived class due to different signatures.
In C++11, if the base class has defined its own move (copy) constructor (assignment operator), does its subclass need to define its own move (copy) constructor (assignment operator) in where call the base class's corresponding constructor/operator is called explicitly?
Is it a good idea to define the constructor, destructor, move/copy constructor (assignment operator) clearly every time?
struct Base {
Base() {}
Base(Base&& o);
};
struct Sub : public Base {
Sub(Sub&& o) ; // Need I do it explicitly ? If not,what the compiler will do for me
};
The compiler will generate a default move constructor if you don't specify one in the base class (except some cases, e.g. there's a base class with a deleted move constructor) but you should, in any case, call explicitly the base class' one if you have it:
Sub(Sub&& o) : Base(std::move(o))
According to the standard (N3797) 12.8/9 Copying and moving class objects [class.copy]:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator, and
— X does not have a user-declared destructor.
As such, if your class meets the above requirements then a default move constructor will be implicitly declared for you.
As already being stated, the base-class has no knowledge of any sub-classes. As such, whether you declare a move constructor in one base class has no effect on the implicit generation of a move constructor in its sub-classes.
As far as it concerns whether you should declare explicitly a constructor/destructor etc. of a class, there's this nice article.
No, you don't have. I'll be automatically generated like default/copy constructor.
From this page,
Implicitly-declared move constructor
If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
there are no user-declared copy constructors
there are no user-declared copy assignment operators
there are no user-declared move assignment operators
there are no user-declared destructors
(until C++14) the implicitly-declared move constructor is not defined as deleted due to conditions detailed in the next section
then the compiler will declare a move constructor as an inline public member of its class with the signature T::T(T&&).
A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
Your struct Sub has no user-declared copy constructors, copy assignment operators, move assignment operators or destructors.
And,
Trivial move constructor
The move constructor for class T is trivial if all of the following is true:
It is not user-provided (meaning, it is implicitly-defined or defaulted), and if it is defaulted, its signature is the same as implicitly-defined
T has no virtual member functions
T has no virtual base classes
The move constructor selected for every direct base of T is trivial
The move constructor selected for every non-static class type (or array of class type) member of T is trivial
T has no non-static data members of volatile-qualified type
(since C++14)
A trivial move constructor is a constructor that performs the same action as the trivial copy constructor, that is, makes a copy of the object representation as if by std::memmove. All data types compatible with the C language (POD types) are trivially movable.
Implicitly-defined move constructor
If the implicitly-declared move constructor is neither deleted nor trivial, it is defined (that is, a function body is generated and compiled) by the compiler. For union types, the implicitly-defined move constructor copies the object representation (as by std::memmove). For non-union class types (class and struct), the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument.
The move constructor of Base is not trivial (it's user-defined). So, the implicitly-defined move constructor of Sub will work as "the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument."
Consider the following class:
class A
{
public:
std::string field_a;
std::string field_b;
}
Now consider the following copy construction:
A a1(a2);
The copy construction will adequately copy A despite the lack of of an explicit copy constructor because the copy constructors for std::string will be called by the compiler generated implicit copy constructor.
What I wish to know is, is the same true for move construction?
EDIT: Testing here shows that:
A a2(std::move(a1));
Will actually result in a copy construction, unless the specific move constructor:
A( A && other ) : a(std::move(other.a)) {}
Is defined.
EDIT EDIT
I pinged Stephan T Lavavej and asked him why VC 2012 doesn't seem to follow what draft 12.8 states regarding implicit move constructor generation. He was kind enough to explain:
It's more of a "feature not yet implemented" than a bug. VC currently
implements what I refer to as rvalue references v2.0, where move
ctors/assigns are never implicitly generated and never affect the
implicit generation of copy ctors/assigns. C++11 specifies rvalue
references v3.0, which are the rules you're looking at.
Yes, from the C++11 draft, 12.8:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator,
X does not have a user-declared destructor, and
the move constructor would not be implicitly defined as deleted.
The last condition is specified with more detail later:
An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3) if X has:
a variant member with a non-trivial corresponding constructor and X is a union-like class,
a non-static data member of class type M (or array thereof) that cannot be copied/moved because
overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a
function that is deleted or inaccessible from the defaulted constructor,
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3), as
applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted or
inaccessible from the defaulted constructor,
any direct or virtual base class or non-static data member of a type with a destructor that is deleted
or inaccessible from the defaulted constructor,
for the copy constructor, a non-static data member of rvalue reference type, or
for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.
Plainly speaking, the move constructor will be implicitly declared if:
The class does not have user-declared any of the other special member functions.
The move constructor can be sensibly implemented by moving all its members and bases.
Your class obviously complies with these conditions.
The compiler synthesizes a move constructor if it can and if there is no user-defined copy constructor. The restriction that no move constructor is synthesized if there is copy constructor is intended to avoid breaking existing code. Of course, all members need to be movable. The exact rules are a bit more involved.
I guess it is so, but I am looking for C++11 language lawyers to confirm my impression. Is it true that the following class
struct X{
X(){}
X(X const&)=default;
};
will not be automatically move-enabled, i.e., getting X(X&&) and operator=(X&&), because its copy constructor is "user-declared", even though it looks equivalent to
struct X{
};
which will get both X(X const&) and X(X&&) etc., implicitely declared and (trivially) defined on use.
From the standard:
8.4.2 Explicitly-defaulted functions [dcl.fct.def.default]
4 - [...] A special member function is user-provided if it is user-declared and not explicitly
defaulted or deleted on its first declaration. [...]
An explicit default can be combined with its declaration, or it can be separate:
struct S {
S();
};
S::S() = default;
In either case its (first) declaration makes it user-declared.
Yes, your defaulted copy assign operator precludes the implicit move ctor.
BTW putting =default is actually a definition. I remember trying to implement a pimpl idiom with std::unique_ptr and having to remove =default from headers and putting them in the implementation file because the destructor for unique_ptr needed the definition of the class it is trying to clean up.
A defaulted copy constructor is indeed "user-declared"; I think the addition of default was in fact the reason why they changed the term from "user-defined" to "user-declared".
That is correct, §12.8 sets the conditions when a move constructor gets implicitly declared and the presence of a user-declared copy constructor precludes that. You cannot have
user-declared copy constructor
user-declared copy assignment operator
user-declared move-assignment operator
user-declared destructor
Am I doing this right? This is a highly simplified version of my code:
class Logger {
public:
Logger(std::ostream) { /*...*/}
};
class Driver {
public:
Driver() : m_logger(std::cout) {}
Driver(Logger& logger) : m_logger(logger) {}
private
Logger m_logger;
};
So my class Driver has a member of type Logger. When I call the argument-less constructor Driver(), the instance of Driver creates its own instance of Logger using std::cout.
When calling Driver(Logger) the instance shall use an already existing instance of Logger passed as a reference.
The above code compiles using g++. Although I understand what happens when calling Driver(), I don't get what happens when calling Driver(Logger). Logger has no constructor which accepts a reference on Logger as argument ("copy constructor"). So what is executed when calling Driver(Logger)?
A trivial copy constructor for Logger is synthesised for you, unless you declare one yourself.
This is much the same as how a trivial default constructor is synthesised for you (if you don't declare a default user-defined constructor).
[C++11: 12.8/7]: If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor
is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. Thus, for the class definition
struct X {
X(const X&, int);
};
a copy constructor is implicitly-declared. If the user-declared constructor is later defined as
X::X(const X& x, int i =0) { /* ... */ }
then any use of X’s copy constructor is ill-formed because of the ambiguity; no diagnostic is required.
[C++11: 12.8/8]: The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)
if
each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.
Otherwise, the implicitly-declared copy constructor will have the form
X::X(X&)
When you construct a Driver with a Logger argument, it is taken by reference and the Logger copy constructor is invoked to initialize m_logger, so you end up with a new Logger that is a copy of the argument. The copy constructor is provided by the compiler unless you explicitly make Logger non-copyable, by declaring the copy constructor private.
Seems perfectly valid, except that you should make the logger argument a const reference; you're going to copy it, after all, not modify it.