Move member function generation - c++

Code:
#include <iostream>
#include <ios>
#include <string>
#include <type_traits>
#include <memory>
struct value
{
~value() = default;
std::unique_ptr<std::string> s;
};
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_move_constructible<value>::value << '\n';
std::cout << std::is_move_assignable<value>::value << '\n';
using str_ptr = std::unique_ptr<std::string>;
std::cout << std::is_move_constructible<str_ptr>::value << '\n';
std::cout << std::is_move_assignable<str_ptr>::value << '\n';
return 0;
}
Output (compiled with g++ v4.7.2, http://ideone.com/CkW1tG):
false
false
true
true
As I expect, value is not move constructible and is not move assignable because:
~value() = default;
is a user-declared destructor, which prevents the implicit generation of move members according to section 12.8 (see below).
If the destructor is removed then value is move constructible and move assignable, as I expect (http://ideone.com/VcR2eq).
However, when the definition of value is changed to (http://ideone.com/M8LHEA):
struct value
{
~value() = default;
std::string s; // std::unique_ptr<> removed
};
the output is:
true
true
true
true
value is unexpectedly move constructible and move assignable.
Am I misunderstanding or is this a compiler bug?
Background: I provided an answer to this question and was informed that Tree<> was moveable, but I am unsure and am attempting to determine for certain if it is or not.
Section 8.4.2 Explicitly-defaulted functions of the c++11 standard (draft n3337):
Explicitly-defaulted functions and implicitly-declared functions are
collectively called defaulted functions, and the implementation shall
provide implicit definitions for them (12.1 12.4, 12.8), which might mean
defining them as deleted.
A special member function is user-provided if it is user-declared and
not explicitly defaulted or deleted on its first declaration.
A user-provided explicitly-defaulted function (i.e., explicitly defaulted
after its first declaration) is defined at the point where it is
explicitly defaulted; if such a function is implicitly defined as deleted,
the program is ill-formed. [ Note: Declaring a function as defaulted after its
first declaration can provide efficient execution and concise definition while
enabling a stable binary interface to an evolving code base.—end note ]
Section 12.8 Copying and moving class objects (point 9):
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.

std::is_move_constructible<T> is true iff std::is_constructible<T, T&&> is true, but that doesn't imply that such a construction will call a move constructor, only that it is possible to construct the type from an rvalue of the same type. Such a construction might use a copy constructor.
When value::s is a unique_ptr the type's copy constructor and copy assignment operator are defined as deleted, because the s member is not copyable. It does not have a move constructor and move assignment operator because, as you pointed out, it has a user-declared destructor. That means it has no copy constructor and no move constructor (and no other user-defined constructors that could accept an argument of type value&&) so std::is_constructible<value, value&&> is false.
When value::s is a string the type's copy constructor and copy assignment operator are not defined as deleted, because the s member is copyable, and so value is also copyable, and a CopyConstructible type is also MoveConstructible, because it's valid in this context:
value v1;
value v2 = std::move(v1); // calls copy constructor
That means std::is_constructible<value, value&&> is true, even though it invokes the copy constructor not a move constructor.

Related

VS2017: Copy Constructor doesn't work when move constructor is deleted [duplicate]

I'm trying to use this code to demonstrate the use of the copy-constructor. My presumption was that when I have a function that returns by value, my compiler will, by default, perform a move of the object. But when the move-constructor is unavailable, the compiler will copy instead (in C++03, the compiler would copy when returning by-value). So why in the following example does the compiler try to call the explicitly-deleted move-constructor instead of the available copy-constructor? I'm compiling this in GCC 4.7.2.
struct S
{
S() = default;
S(S const &) = default;
S(S&&) = delete;
};
S f() { return S{}; }
int main()
{
f();
}
prog.cpp: In function ‘S f()’:
prog.cpp:8:18: error: use of deleted function ‘S::S(S&&)’
prog.cpp:5:5: error: declared here
Deleted move members are evil. They are not outlawed, because some day, someone will find a clever use for them. But I haven't seen a good use yet.
Deleting a special member is not the same thing as not having a special member. Nowhere is this more obvious than with the move constructor and move assignment operator.
When present, whether deleted, defaulted, or user-defined, the move constructor and the move assignment operator participate in overload resolution. That means that they "compete" with the special copy members. The copy members will typically favor const lvalues, whereas the move members attract rvalues.
When returning a local type from a function (when the local type is the same un-cv-qualified type as the return type), the return statement first considers the return expression as an rvalue, and only if it can't find a suitable constructor, will it then be considered as an lvalue. I.e. matching the proper constructor for returning a local object from a function is a 2-phase operation.
When you don't have a move constructor at all (not even deleted), but you do have a normal copy constructor (takes a const &), then the rvalue from the return statement will match the copy constructor.
When you do have a move constructor, even it is marked deleted, the rvalue from the return statement will find the move constructor a better match than the copy constructor.
Summary
Unless you really know what you are doing, never delete the move members. If you don't want your type to be movable, just do not define the move members and make sure that you do declare copy members, even if the copy members are =default'd.
Update
I suppose it's hard to provide quotes from the Standard on what delete
doesn't do? – DyP
8.4.3 Deleted definitions [dcl.fct.def.delete]
2 A program that refers to a deleted function implicitly or
explicitly, other than to declare it, is ill-formed. [ Note: This
includes calling the function implicitly or explicitly and forming a
pointer or pointer-to-member to the function. It applies even for
references in expressions that are not potentially-evaluated. If a
function is overloaded, it is referenced only if the function is
selected by overload resolution. — end note ]
Update 2
12.8 Copying and moving class objects [class.copy]
9 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.
[Note: When the move constructor is not implicitly declared or
explicitly supplied, expressions that otherwise would have invoked the
move constructor may instead invoke a copy constructor. — end note ]

C++ object as parameter, but no constructor for it [duplicate]

I want to refresh my memory on the conditions under which a compiler typically auto generates a default constructor, copy constructor and assignment operator.
I recollect there were some rules, but I don't remember, and also can't find a reputable resource online. Can anyone help?
In the following, "auto-generated" means "implicitly declared as defaulted, but not defined as deleted". There are situations where the special member functions are declared, but defined as deleted.
The default constructor is auto-generated if there is no user-declared constructor (§12.1/5).
The copy constructor is auto-generated if there is no user-declared move constructor or move assignment operator (because there are no move constructors or move assignment operators in C++03, this simplifies to "always" in C++03) (§12.8/8).
The copy assignment operator is auto-generated if there is no user-declared move constructor or move assignment operator (§12.8/19).
The destructor is auto-generated if there is no user-declared destructor (§12.4/4).
C++11 and later only:
The move constructor is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move constructor is valid (§12.8/10).
The move assignment operator is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move assignment operator is valid (e.g. if it wouldn't need to assign constant members) (§12.8/21).
I've found the diagram below very useful.
from Sticky Bits - Becoming a Rule of Zero Hero
C++17 N4659 standard draft
For a quick cross standard reference, have a look at the "Implicitly-declared" sections of the following cppreference entries:
https://en.cppreference.com/w/cpp/language/copy_constructor
https://en.cppreference.com/w/cpp/language/move_constructor
https://en.cppreference.com/w/cpp/language/copy_assignment
https://en.cppreference.com/w/cpp/language/move_assignment
The same information can of course be obtained from the standard. E.g. on C++17 N4659 standard draft:
15.8.1 "Copy/move constructors" says for for copy constructor:
6 If the class definition does not explicitly declare a copy constructor, a non-explicit 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 (11.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor.
and for move constructor:
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.
15.8.2 "Copy/move assignment operator" says for copy assignment:
2 If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared
copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (11.4). The latter
case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.
and for move assignment:
4 If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly
declared as defaulted if and only if
(4.1) — X does not have a user-declared copy constructor,
(4.2) — X does not have a user-declared move constructor,
(4.3) — X does not have a user-declared copy assignment operator, and
(4.4) — X does not have a user-declared destructor.
15.4 "Destructors" says it for destructors:
4 If a class has no user-declared destructor, a destructor is implicitly declared as defaulted (11.4). An
implicitly-declared destructor is an inline public member of its class.

Does deleting a copy constructor or copy assignment operator count as "user declared"?

Per this presentation, if either the copy constructor or copy assignment operator is "user declared", then no implicit move operations will be generated. Does deleteing the copy constructor or copy assignment operator count as "user declared"?
struct NoCopy {
NoCopy(NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
};
Will implicit move operations be generated for the NoCopy class? Or does deleting the relevant copy operations count as "user declared" and thus inhibit implicit move generation?
If possible, I'd prefer an answer referencing the relevant parts of the standard.
According to slide 14 of your presentation, a deleted copy constructor is "user declared" thus inhibiting the move generation.
The term "user declared" doesn't have a formal definition in the standard. It is meant to be the opposite of "implicitly declared" in the context of special member functions. [dcl.fct.def.default]/4 could be a bit clearer about this fact, but the intention is there:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 12.4, 12.8), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
Both NoCopy(NoCopy&) = delete; and NoCopy& operator=(const NoCopy&) = delete; are declarations of special member functions. Since you are explicitly declaring them, as opposed to allowing the compiler to declare them implicitly, they are user-declared. Those declarations will therefore suppress the implicit declarations of the move constructor and move assignment operator per [class.copy]/9:
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.

Move constructor template elision

For what reason move constructor call is not elided in following code?
struct Foo {
Foo() = default;
Foo(const Foo&) { cout << "Foo(const Foo&)" << endl; }
template<class T> Foo(T&&) { cout << "Foo<T>(T&&)" << endl; }
};
auto f = Foo{};
Output: Foo<T>(T&&)
Checked on clang 3.3, g++ 4.9.
Adding defaulted or user-defined move constructor results in no output at all. Why the call to move constructor template is not elided by the compiler? And why the call to non-template one gets elided even if it's user-defined (i.e. how does the compiler know that it has no side-effects and can be safely elided)?
To be a move constructor and thus candidate for elision, the constructor must not be a template instantiation (similarly, the fact that a template can generate a constructor with the same signature will not prevent the defaulted version to be generated).
In your example, the presence of the explicit copy constructor prevent the implicit generation of a default move constructor, so the constructor template is instantiated (and is not a move constructor even if it share the signature).
If you add an explicit (default or not) move constructor, it will be used (and can -- but not must -- be elided).
The reason is because copy and move constructors are defined to be non-template (12.8/2, 12.8/3), copy/move elision rules do not apply for this constructor template (12.8/31).
The addition of defaulted or user defined move constructor (in this case) results in different output (and move elision) because user-defined copy constructor prevents compiler from implicitly generating a default one, thus the answer was wrong in first place:
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).

C++11 non-static member initializers and deleted copy constructor

I'm trying to compile the following simple code with GCC 4.7.2 (MinGW). Here I'm using C++11 feature - non-static member initializers:
#include <iostream>
using namespace std;
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
};
struct B
{
A a = 7;
};
int main()
{
B b;
cout << "b.a.var = " << b.a.var;
return 0;
}
This code doesn't compile because of deleted copy-constructor which isn't necessary here. Here are errors:
main.cpp:27:11: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
main.cpp: In constructor 'constexpr B::B()':
main.cpp:25:8: error: use of deleted function 'A::A(const A&)'
main.cpp:13:5: error: declared here
If I implement copy-constructor like this:
A(const A& a)
{
cout << "A(const A&)\n";
var = a.var;
}
Then code compiles fine and program gives me expected output:
A(int i)
b.a.var = 7
So it means that copy constructor is not used, but why I can't delete it?
Edit: Thanks for your answers. Copy or move constructor is required by standard if I'm using =. To fix this problem I need to implement move constructor or use direct initialization syntax A a{7}.
The initialiser for a gives you copy-initialization:
A a = 7;
For such a copy-initialization, where a user-defined conversion is required, the resulting initialisation is equivalent to:
A a(A(7));
That is, a temporary A is constructed and then passed to the copy constructor of your a object. This copying may be elided, but the copy constructor must be available nonetheless. In other words, the copying can only be elided if the copy would be possible in the first place. If you delete the copy constructor, the copying is impossible.
You'll have a better time with your deleted copy constructor if you do the following:
A a{7};
This does direct-initialization and no copy constructor is required.
Copy initialization is allowed to elide the copy but the copy constructor is mandated to be accessible by the standard.
Per Paragraph 12.2/14 of the C++11 Standard:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]
The reason why your copy-initialization doesn't compile is that during copy-initialization a temporary object needs to be created (at least logically), and the object being initialized shall be constructed from it.
Now all the previous answers seem to focus just on copy-constructors, but the first problem here is the absence of a move-constructor. As long as you provide one, then it is true that the copy constructor is not necessary.
Alas, deleting the copy constructor prevents the generation of an implicit move constructor. Adding one explicitly would fix the problem:
struct A
{
int var;
A()
{
cout << "A()\n";
}
A(int i)
{
cout << "A(int i)\n";
var = i;
}
A(const A&) = delete;
// THIS MAKES IT WORK
A(A&& a)
{
cout << "A(A&&)\n`;
var = a.var;
}
};
Notice that when both the move-constructor and the copy-constructor are present, the move-constructor is preferred, because the temporary created for copy-initializing your object is an rvalue.
When a move-constructor is absent, the compiler can invoke the copy constructor to perform the initialization, because constant lvalue references can bind to rvalue references and copy is seen as an unoptimized move.
However, even though the compiler is allowed to elide the call to the move or the copy constructor, the semantics of the operation must still be checked. Per Paragraph 12.8/32 of the C++11 Standard:
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ] [...]
Therefore, an error is issued by the compiler if neither the move constructor nor the copy constructor are present.
If you want, however, you can direct-initialize your object rather than copy-initializing it. Just use the direct-initialization syntax instead:
struct B
{
A a{7};
};
This will make the move-constructor and copy-constructor unnecessary, because no temporary is created when direct-initializing an object.
This code doesn't compiles because of deleted copy-constructor which isn't necessary here
Sorry, but your copy constructor is necessary. Even though the copy can be optimised out, it must still be possible in the code. This mandated by the language.
So it means that copy constructor is not used, but whyI can't delete it?
In your case, copy constructor is used only for semantic-check as required by the Standard, it also needs to be accessible. Later on, the compiler optimizes the code, eliding the call to the copy-constructor, so it is not actually invoked.