2

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

Community
  • 1
  • 1
Sap
  • 914
  • 1
  • 6
  • 20
  • 3
    Do you expect a different answer than what you got in [**your previous question**](http://stackoverflow.com/questions/24678701/move-constructor-is-required-even-if-it-is-not-used-why)? – juanchopanza Jul 11 '14 at 07:06
  • Or you are trying to ask why the implicitly declared move constructor doesn't work in the first example? That would actually be a good question (which can be asked with 1/4 of the lines of code.) – juanchopanza Jul 11 '14 at 07:15
  • Yes I am trying to aks why it does require a move-constrcutor even if the one is prohibited to use in this particular case. – Sap Jul 11 '14 at 07:25
  • OK, I added an answer to that question. – juanchopanza Jul 11 '14 at 07:26
  • In the first one, it doesn't have a move constructor **because of the destructor** . Not any other reason. And the move constructor is not odr-used here. If you remove the destructor then it compiles because there *is* now an implicitly-declared move constructor, which beats the copy-constructor on overload resolution. – M.M Jul 11 '14 at 07:55
  • If move-constrcutor is NOT "odr-used" then why does it show an error about copy-constrcutor? It should throw an error about copy-constructor, shouldn't it? – Sap Jul 11 '14 at 08:01
  • Eh? It does show an error about copy-constructor. There is no move-constructor in case 1. – M.M Jul 11 '14 at 08:02
  • 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&&) – Sap Jul 11 '14 at 08:13
  • That is a detail. It tries to use the copy constructor first, there isn't one, and there is not move constructor either. It has to report *something*. In this case, it is a bit misleading. – juanchopanza Jul 11 '14 at 08:31
  • But it doesn't try to use copy constructor if it has no MOVE constructor. – Sap Jul 11 '14 at 09:08

3 Answers3

5

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.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Okay okay. I see. I guess it doesn't concern about "copy elision".The MOVE-constructor CAN be used there and the class SHOULD HAVE an impiled MOVE-constructor. Why it throws an error about the COPY-constructor? – Sap Jul 11 '14 at 07:11
  • 1
    You said there was no move-constructor. Why? All class's fields have move-constructors and class does not have any copy/move constructors or deleted onces, so why it doesn't have implied move-constructor? – Sap Jul 11 '14 at 07:15
  • And also look and the example once more - it requires a COPY-constructor but if fact it needs a MOVE one. – Sap Jul 11 '14 at 07:16
  • @user3544995 updated post to answer all those queries – M.M Jul 11 '14 at 07:27
  • Thanks for the great answer!!! The only one thing is missed - what is the difference between copy/move constructors resulted of? They both are odr-used, but for a move-constructor the compiler falls back to the copying one, and for a copy-constructor it tries to implement it. – Sap Jul 11 '14 at 07:34
  • Overload resolution happens before *odr-use* is decided. Overload resolution is complicated but it basically boils down to the fact that we are trying to bind a temporary, and the candidate functions are `A(A const &)` and `A(A &&)`. There's nothing special about constructors in this case - an rvalue will prefer to bind to an rvalue-reference than an lvalue-reference. If your object has the move-constructor (be it implicitly-defined or explicitly) then it is used (and odr-used at that point); and the copy-constructor is not odr-used. – M.M Jul 11 '14 at 07:39
  • There isn't a "fallback procedure" other than overload resolution. I'll edit my post to clarify that – M.M Jul 11 '14 at 07:40
  • Now I see :) Both copy and move constructors were deleted (unimplementable) because unique_ptr has deleted copy constructor. But copmilers falss back from move to copy because of "odr-use". That's why there is a "no copy contructor" error but not a "no move contrcutor". – Sap Jul 11 '14 at 07:42
  • Not exactly - in case 1, the move constructor was not declared at all (this is different to being *defined as deleted*) because the presence of a user-declared destructor suppresses it. In case 2 you manually declare the move constructor. The "fall back" is a feature of overload resolution; odr-use does not kick in until after overload resolution. – M.M Jul 11 '14 at 07:44
  • Then why does it broke if I make copy constructor default and delete move constructor? It should fall back to copy constructor but it doesn't. :) – Sap Jul 11 '14 at 08:35
3

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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • An implicitly-defined copy-constructor which is deleted, does not count as a user-declared copy constructor. If you remove the destructor definition from OP's first example, it compiles – M.M Jul 11 '14 at 07:49
  • Matt, that's the only thing I can't get. Why does destrcutor have an influence on constructor generation? – Sap Jul 11 '14 at 07:54
  • 2
    @user3544935 Because the standard says so. I mention this in my post (second para after the horizontal break) . I'm not exactly sure why they put this rule in , but it's there. I guess it is because if a class does have a user-defined destructor then it might be because there are members that need special creation/copying/deletion, so the implicitly-generated move constructor might do the wrong thing. (It'd be nice to retroactively apply this to copy-constructors too but that would break existing code). – M.M Jul 11 '14 at 07:57
  • Okay' I'll check about the standard - I dont remeber that a destructor supresses defulat constructors generation. – Sap Jul 11 '14 at 08:07
  • But why then does it stop with error at copying constrcutor and not moving? If the moving constrcutor is absent and is prohibited to be generated it must stop with error on it and not fall back to copying constrcutor. – Sap Jul 11 '14 at 08:08
  • @MattMcNabb Yikes! Good catch. Fixed the answer. – juanchopanza Jul 11 '14 at 08:30
  • @user3544995 Note that the rules on implicit creation of move constructors and assignment operators are particularly hard because they can actually break exception guarantees that applied in C++03 code. – juanchopanza Jul 11 '14 at 08:43
1

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.

nwp
  • 9,623
  • 5
  • 38
  • 68
  • Okay. But if I delete MOVE-constructor the program could not be compiled. So it IS required (but a copying one). – Sap Jul 11 '14 at 07:19
  • @user3544995 If you delete the move constructor by deleting the code and you have a copy constructor it will compile. If you delete the move constructor with `A(A&&)=delete;` then you explicitly forbade the compiler to move even though it could do so with a copy constructor. You can express the behavior you want fairly well like that. – nwp Jul 11 '14 at 07:44
  • NO it WILL NOT. Try it. A(A&&)=delete; A(const A&)=default; And you will get an error about 'A::A(A&&)' absence. – Sap Jul 11 '14 at 08:27
  • @user3544995 That is the same thing that I said. Also you do not get an error about `A::A(A&&)` being absent, you get an error about `A::A(A&&)` being deleted. If it is absent it compiles. – nwp Jul 11 '14 at 08:59