How function matching works when using move and copy semantics? - c++

I have some ambiguities about "move" semantics: What I've read is that a move constructor or assignment is defined implicitly as a deleted function if the class has defined one of its own copy-control members. But I have this code:
int main()
{
struct A {
A() = default;
A(const A&) { cout << "A's cpy-ctor\n"; } // this forces move ctor to be defined as a deleted function
//A(A&&) = default;
//A(A&&) = delete; // if uncomment this line then the line below calling std::move will cause an error(referencing a deleted function).
};
A a = std::move(A{}); // move not available then use copy-ctor instead
std::cout << "\ndone\n";
}
If I uncomment the first commented line then it is OK as I've guessed: the copy-constructor is used instead of the move-ctor as the fact it is not explicitly defined.
But if I uncomment the second commented line I'll get compile-time error on calling std::move complaining about a deleted function. But why the compiler doesn't use copy-ctor instead directly?
What does mean defulting this move-ctor and how does that affect function-matching?
Thank you so much!

When you have a user declared copy constructor, then the move constructor and move assignment are not declared which is not the same as deleted.
This means that the only valid signature is A(A const &) - the copy constructor.
So, when you call the constructor with an rvalue reference (what std::move explicitly provides), the copy constructor whose signature is a lvalue reference to const will be the best match.
However, if you define the move constructor as deleted, then that signature is found, and since it is the best match the compiler does not even try to match with the copy constructor. However, since the move constructor is deleted, the compiler says "hey I found the best match, which is the move constructor, so I'll use that one, but it is defined as deleted, so you are not allowed to call it."

Related

RVO and deleted move constructor in C++14

I have been learning about (N)RVO for a last few days. As I read on cppreference in the copy elision article, for C++14 :
... the compilers are permitted, but not required to omit the copy- and move- (since C++11)construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
So, either copy or move constructor must be present and accessible. But in the code bellow:
#include <iostream>
class myClass
{
public:
myClass() { std::cout << "Constructor" << std::endl; }
~myClass() { std::cout << "Destructor" << std::endl; }
myClass(myClass const&) { std::cout << "COPY constructor" << std::endl;}
myClass(myClass &&) = delete;
};
myClass foo()
{
return myClass{};
}
int main()
{
myClass m = foo();
return 0;
}
I got the following error: test.cpp: In function 'myClass foo()':
test.cpp:15:17: error: use of deleted function 'myClass::myClass(myClass&&)'
return myClass{};. I get this error even if I don't call foo() from the main(). The same issue with NRVO.
Hence the move constructor is always required, isn't it? (while the copy is not, I checked it)
I do not understand where compiler needs a move constructor. My only guess is that it might be required for constructing a temporary variable, but it sounds doubtful. Do someone knows an answer?
About the compiler: I tried it on g++ and VS compilers, you can check it online: http://rextester.com/HFT30137.
P.S. I know that in C++17 standard the RVO is obligated. But the NRVO isn't, so I want to study out what is going on here to understand when I can use NRVO.
Cited from cppreference:
Deleted functions
If, instead of a function body, the special syntax = delete ; is used, the function is defined as deleted.
...
If the function is overloaded, overload resolution takes place first, and the program is only ill-formed if the deleted function was selected.
It's not the same thing if you explicitly define the move constructor to be deleted. Here because of the presence of this deleted move constructor, although the copy constructor can match, the move constructor is better, thus the move constructor is selected during overload resolution.
If you remove the explicit delete, then the copy constructor will be selected and your program will compile.

Why does C++ allow you to move classes containing objects with deleted move operations?

Why am I allowed to use std::move on a class which contains fields of a type with deleted move semantics (case 1), but am not allowed to use it on an instance of such a class (case 2)?
I understand case 2. I have explicitly deleted the move constructor, so I get an error if I try to move it. But I would expect that this would also be the case in case 1, where such class is also being moved.
class TestNonMovable {
std::string ss;
public:
TestNonMovable(){}
TestNonMovable(const TestNonMovable&) {}
TestNonMovable(TestNonMovable&&) = delete;
};
class SomeClass {
TestNonMovable tnm;
};
int main() {
// case1: This compiles, my understanding is that for SomeClass::tnm compiler will use copy constrctor
SomeClass sc1;
SomeClass sc2 = std::move(sc1);
// case2: This does not compile, move constructor is explicitly deleted, compiler will not try using copy constructor
TestNonMovable tnm;
TestNonMovable tnm2 = std::move(tnm); //error: use of deleted function 'TestNonMovable::TestNonMovable(TestNonMovable&&)'
}
Note the difference between the two classes. For TestNonMovable (case 2), you explicitly declare the move constructor as delete. With TestNonMovable tnm2 = std::move(tnm); the deleted move constructor is selected in overload resolution and then cause the error.
For SomeClass (case 1), you don't explicitly declare the move and copy constructor. The copy constructor will be implicitly declared and defined, but the move constructor will be implicitly declared and defined as deleted because it has a non-movable data member. Note the deleted implicitly declared move constructor won't participate in overload resolution. With SomeClass sc2 = std::move(sc1);, the copy constructor is selected and then the code works fine. The rvalue reference returned from std::move could be bound to lvalue-reference to const (i.e. const SomeClass&) too.
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue). (since C++14)
BTW: std::move itself is allowed in both cases. It doesn't perform the move operation, it just converts the argument to rvalue. The move operation happens at construction in both cases.

Enforcing explicitly defaulted special member function generation

In C++11, one can explicitly default a special member function, if its implicit generation was automatically prevented.
However, explicitly defaulting a special member function only undoes the implicit deletion caused by manually declaring some of the other special member functions (copy operations, destructor, etc.), it does not force the compiler to generate the function and the code is considered to be well formed even if the function can't in fact be generated.
Consider the following scenario:
struct A
{
A () = default;
A (const A&) = default;
A (A&&) = delete; // Move constructor is deleted here
};
struct B
{
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
The move constructor in B will not be generated by the compiler, because doing so would cause a compilation error (A's move constructor is deleted). Without explicitly deleting A's constructor, B's move constructor would be generated as expected (copying A, rather than moving it).
Attempting to move such an object will silently use the copy constructor instead:
B b;
B b2 (std::move(b)); // Will call B's copy constructor
Is there a way to force the compiler into either generating the function or issue a compilation error if it can't? Without this guarantee, it's difficult to rely on defaulted move constructors, if a single deleted constructor can disable move for entire object hierarchies.
There is a way to detect types like A. But only if the type explicitly deletes the move constructor. If the move constructor is implicitly generated as deleted, then it will not participate in overload resolution. This is why B is movable even though A is not. B defaults the move constructor, which means it gets implicitly deleted, so copying happens.
B is therefore move constructible. However, A is not. So it's a simple matter of this:
struct B
{
static_assert(is_move_constructible<A>::value, "Oops...");
B () = default;
B (const B&) = default;
B (B&&) = default; // Move constructor is defaulted here
A a;
};
Now, there is no general way to cause any type which contains copy-only types to do what you want. That is, you have to static assert on each type individually; you can't put some syntax in the default move constructor to make attempts to move B fail.
The reason for that has to do in part with backwards compatibility. Think about all the pre-C++11 code that declared user-defined copy constructors. By the rules of move constructor generation in C++11, all of them would have deleted move constructors. Which means that any code of the form T t = FuncReturningTByValue(); would fail, even though it worked just fine in C++98/03 by calling the copy constructor. So the move-by-copy issue worked around this by making these copy instead of moving if the move constructor could not be generated.
But since = default means "do what you would normally do", it also includes this special overload resolution behavior that ignores the implicitly deleted move constructor.

Understanding Default Move Constructor Definition

While reading about the move constructor from the current standard, I can see the following about this:
12.8 Copying and moving class objects
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 ]
I think note section clearly mentions that fall-back for default move constructor would be copy constructor. To understand this concept I wrote the small program to understand this concept.
#include<iostream>
struct handleclass {
public:
handleclass():length{0}, p{nullptr} {}
handleclass(size_t l):length{l},p{new int[length]} { }
~handleclass() { delete[] p; }
size_t length;
int* p;
};
handleclass function(void) {
handleclass x(10);
return x;
}
int main() {
handleclass y;
std::cout<<y.length<<std::endl;
y = function();
std::cout<<y.length<<std::endl;
handleclass a;
handleclass b(10);
a = std::move(b);
return 0;
}
Obviously this program is incorrect and would have undefined behaviour(terminate) due to the shallow copy of resources by two objects. But my focus is to understand the default move constructor generated and used in program. I hope this example make sense.
In the above program, in both cases where move constructor should be called, it appears to me that compiler is using default copy constructor.
Based on the above rule mentioned in the standard I think we should have got the compiler error as now program explicitly trying to call the move constructor and neither user has implemented nor compiler generates default(implicitly) as above rule does not satisfy?.
However this is getting compiled without any warning/error and running successfully. Could somebody explains about default(implicitly) move constructor concepts? Or I am missing something?.
You're forgetting about copy elision which means that y = function(); may not actually invoke any copy or move constructors; just the constructor for x and the assignment operator.
Some compilers let you disable copy elision, as mentioned on that thread.
I'm not sure what you mean by "in both cases where move constructor should be called". There are actually zero cases where move constructor should be called (your object does not have a move constructor), and one case where copy constructor could be called (the return statement) but may be elided.
You have two cases of assignment operator: y = function(); and a = std::move(b); . Again, since your class does not have a move-assignment operator, these will use the copy-assignment operator.
It would probably help your testing if you added code to your object to cout from within the copy constructor and move constructor.
Indeed, there is no implicit move constructor due to the user-declared destructor.
But there is an implicit copy constructor and copy-assignment operator; for historical reasons, the destructor doesn't inhibit those, although such behaviour is deprecated since (as you point out) it usually gives invalid copy semantics.
They can be used to copy both lvalues and rvalues, and so are used for the function return (which might be elided) and assignments in your test program. If you want to prevent that, then you'll have to delete those functions.
A move constructor and move assignment operator have not been implicitly declared because you have explicitly defined a destructor. A copy constructor and copy assignment operator have been implicitly declared though (although this behaviour is deprecated).
If a move constructor and move assignment operator have not been implicitly (or explicitly) declared it will fall back to using the copy equivalents.
As you are trying to call move-assignment it will fall back to using copy assignment instead.

Why does the compiler require a copying constructor, need and have moving one and doesn't uses any of them?

I've already tried to ask this question but I wasn't clear enough. So here is one more try. And I am very sorry for my English ;)
Let's see the code:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
void printRef() {
if (ref.get())
cout<<"i="<<*ref<<endl;
else
cout<<"i=NULL"<<endl;
}
A(const int i) : ref(new int(i)) {
cout<<"Constructor with ";
printRef();
}
~A() {
cout<<"Destructor with";
printRef();
}
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
It can not be compiled because unique_ptr has deleted copying constructor.
Orly?!
This class DOES HAVE an implied moving constructor because unique_ptr has one.
Let's do a test:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
void printRef() {
if (ref.get())
cout<<"i="<<*ref<<endl;
else
cout<<"i=NULL"<<endl;
}
A(const int i) : ref(new int(i)) {
cout<<"Constructor with ";
printRef();
}
// Let's add a moving constructor.
A(A&& a) : ref(std::move(a.ref)) {
cout<<"Moving constructor with";
printRef();
}
~A() {
cout<<"Destructor with";
printRef();
}
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
I've added a moving constructor and now the code can be compiled and executed.
Even if the moving constructor is not used.
The output:
Constructor with i=0
Constructor with i=1
Destructor withi=1
Destructor withi=0
Okay...Let's do one more test and remove the copying constructor (but leave the moving one).
I don't post the code, there only one line has been added:
A(const A& a) = delete;
You should trust me - it works. So the compiler doesn't require a copying constructor.
But it did! (a facepalm should be here)
So what's going on? I see it completely illogical! Or is there some sort of twisted logic I don't see?
Once more:
unique_ptr has a moving constructor and has a deleted copying constructor. Compiler requires copying constructor to be present. But in fact the compiler requires a moving constructor (even if it is not used) and doesn't require a copying (because it could be deleted). And as I see the moving constructor is (should be?) present impliedly.
What's wrong with that?
P.S. One more thing - if I delete the moving constructor the program could not be compiled. So the moving constructor is required, but not the copying one.Why does it require copy-constructor if it's prohibited to use it there?
P.P.S.
Big thanks to juanchopanza's answer! This can be solved by:
A(A&& a) = default;
And also big thanks to Matt McNabb.
As I see it now, the moving constructor is absent because unique_ptr doesn't have a copying one the class has a destructor (and the general rule is that default/copying/moving constructors and destructor could be generated by default only all together). Then the compiler doesn't stop at moving one (why?!) and falls back to copying one. At this point the compiler can't generate it and stops with an error (about the copy constructor) when nothing else can be done.
By the way it you add:
A(A&& a) = delete;
A(const A& a) = default;
It could NOT be compiled with error about 'A::A(A&& a)' deletion, There will be no fall back to copying constructor.
P.P.P.S The last question - why does it stop with error at the COPY constructor but not the MOVE constructor?!
GCC++ 4.7/4.8 says: "error: use of deleted function ‘A::A(const A&)’"
So it stops at copy constructor.
Why?! There should be 'A::A(A&&)'
Ok. Now it seems like a question about move/copy constrcutor choosing rule.
I've created the new more specific question here
This is called copy elision.
The rule in this situation is that a copy/move operation is specified, but the compiler is allowed to optionally elide it as an optimization, even if the copy/move constructor had side-effects.
When copy elision happens, typically the object is created directly in the memory space of the destination; instead of creating a new object and then copy/moving it over to the destination and deleting the first object.
The copy/move constructor still has to actually be present, otherwise we would end up with stupid situations where the code appears to compile, but then fails to compile later when the compiler decides not to do copy-elision. Or the code would work on some compilers and break on other compilers, or if you used different compiler switches.
In your first example you do not declare a copy nor a move constructor. This means that it gets an implicitly-defined copy-constructor.
However, there is a rule that if a class has a user-defined destructor then it does not get an implicitly-defined move constructor. Don't ask me why this rule exists, but it does (see [class.copy]#9 for reference).
Now, the exact wording of the standard is important here. In [class.copy]#13 it says:
A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (3.2)
[Note: The copy/move constructor is implicitly defined even if the implementation elided its odr-use (3.2, 12.2). —end note
The definition of odr-used is quite complicated, but the gist of it is that if you never attempt to copy the object then it will not try to generate the implicitly-defined copy constructor (and likewise for moving and move).
As T.C. explains on your previous thread though, the act of doing A a[2] = {0, 1}; does specify a copy/move, i.e. the value a[0] must be initialized either by copy or by move, from a temporary A(0). This temporary is able to undergo copy elision, but as I explain earlier, the right constructors must still exist so that the code would work if the compiler decides not to use copy elision in this case.
Since your class does not have a move constructor here, it cannot be moved. But the attempt to bind the temporary to a constructor of A still succeeds because there is a copy-constructor defined (albeit implicitly-defined). At that point, odr-use happens and it attempts to generate the copy-constructor and fails due to the unique_ptr.
In your second example, you provide a move-constructor but no copy-constructor. There is still an implicitly-declared copy-constructor which is not generated until it is odr-used, as before.
But the rules of overload resolution say that if a copy and a move are both possible, then the move constructor is used. So it does not odr-use the copy-constructor in this case and everything is fine.
In the third example, again the move-constructor wins overload resolution so it does not matter what how the copy-constructor is defined.
I think you are asking why this
A a[2] = { 0, 1 };
fails to compile, while you would expect it to compile because A may have a move constructor. But it doesn't.
The reason is that A has a member that is not copyable, so its own copy constructor is deleted, and this counts as a user declared copy constructor has a user-declared destructor.
This in turn means A has no implicitly declared move constructor. You have to enable move construction, which you can do by defaulting the constructor:
A(A&&) = default;
To check whether a class is move constructible, you can use is_move_constructible, from the type_traits header:
std::cout << std::boolalpha;
std::cout << std::is_move_constructible<A>::value << std::endl;
This outputs false in your case.
The twisted logic is that you are supposed to write programs at a higher abstraction level. If an object has a copy constructor it can be copied, otherwise it cannot. If you tell the compiler this object shall not be copied it will obey you and not cheat. Once you tell it that it can be copied the compiler will try to make the copy as fast as possible, usually by avoiding the copy constructor.
As for the move constructor: It is an optimization. It tends to be faster to move an object from one place to another than to make an exact copy and destroy the old one. This is what move constructors are for. If there is no move constructor the move can still be done with the old fashioned copy and destroy method.