4

Why?! Why C++ requires the class to be movable even if it's not used! For example:

#include <iostream>
using namespace std;

struct A {
    const int idx;
    //   It could not be compileld if I comment out the next line and uncomment
    // the line after the next but the moving constructor is NOT called anyway!
    A(A&& a) : idx(a.idx) { cout<<"Moving constructor with idx="<<idx<<endl; }
   //  A(A&& a) = delete;
    A(const int i) : idx(i) { cout<<"Constructor with idx="<<i<<endl; }
    ~A() { cout<<"Destructor with idx="<<idx<<endl; }
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

The output is (the move constructor is not called!):

Constructor with idx=0
Constructor with idx=1
Destructor with idx=1
Destructor with idx=0

The code can not be compiled if moving constructor is deleted ('use of deleted function ‘A::A(A&&)’'. But if the constructor is not deleted it is not used! What a stupid restriction? Note: Why do I need it for? The practical meaning appears when I am trying to initialize an array of objects contains unique_ptr field. For example:

// The array of this class can not be initialized!
class B {
    unique_ptr<int> ref;
public:
    B(int* ptr) : ref(ptr)
        {  }
}
// The next class can not be even compiled!
class C {
    B arrayOfB[2] = { NULL, NULL };
}

And it gets even worse if you are trying to use a vector of unique_ptr's.

Okay. Thanks a lot to everybody. There is big confusion with all those copying/moving constructors and array initialization. Actually the question was about the situation when the compiler requires a copying consrtuctor, may use a moving construcor and uses none of them. So I'm going to create a new question a little bit later when I get a normal keyboard. I'll provide the link here.

P.S. I've created more specific and clear question - welcome to discuss it!

Community
  • 1
  • 1
Sap
  • 914
  • 1
  • 6
  • 20

2 Answers2

11
A a[2] = { 0, 1 };

Conceptually, this creates two temporary A objects, A(0) and A(1), and moves or copies them to initialise the array a; so a move or copy constructor is required.

As an optimisation, the move or copy is allowed to be elided, which is why your program doesn't appear to use the move constructor. But there must still be a suitable constructor, even if its use is elided.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • How can I know when the constructor is NOT called then? – Sap Jul 10 '14 at 14:18
  • @user3544995 You shouldn't need to care. Your copy/move constructors should not have any additional side effects, but as you've demonstrated, you can introduce a side effect (like printing with `cout`) just to see if its being called or not. – Joseph Mansfield Jul 10 '14 at 14:19
  • @user3544995: Usually, you won't care - the program will behave the same whether or not the move is elided, and the elision will be a pure optimisation. You'll only notice the difference if (as here) it does something other than simply move the object - which would be a strange thing to do. – Mike Seymour Jul 10 '14 at 14:21
  • LOL. Ok, it is 'copy elision' I guess. But shouldn't the compiler to not require the class to be moveable in such case? If it's documented that the moving constructor is not called why it's required to be present? – Sap Jul 10 '14 at 14:22
  • @user3544995 It's worth mentioning that if you *do* have side effects in your copy and move constructors, copy/move elision allows your program to take multiple different but completely valid execution paths. – Joseph Mansfield Jul 10 '14 at 14:22
  • 3
    @user3544995 Because copy/move elision is an optional optimisation. In fact, the compiler is allowed to elide anything at all as long as it doesn't effect the execution of the program (this is called the *as-if rule*). The only reason it's mentioned explicitly for copy/move constructors is to say that it can even happen if the copy/move constructors have side effects (which would effect the execution of the program). – Joseph Mansfield Jul 10 '14 at 14:24
  • 1
    @user3544995: "If it's documented that the moving constructor is not called why it's required to be present?" - it's not, it's documented that the compiler can choose whether or not to call it. It must be present since otherwise the validity of the code would depend on the compiler's optimisation choices. – Mike Seymour Jul 10 '14 at 14:26
6
A a[2] = { 0, 1 };

This is aggregate initialization. §8.5.1 [dcl.init.aggr]/p2 of the standard provides that

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

§8.5 [dcl.init]/p16, in turn, describes the semantics of copy initialization of class type objects:

  • [...]
  • If the destination type is a (possibly cv-qualified) class type:
    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

Since 0 and 1 are ints, not As, the copy initialization here falls under the second clause. The compiler find a user-defined conversion from int to A in your A::A(int) constructor, calls it to construct a temporary of type A, then performs direct initialization using that temporary. This, in turn, means the compiler is required to perform overload resolution for a constructor taking a temporary of type A, which selects your deleted move constructor, which renders the program ill-formed.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Oh. Thanx. But it still seems strange - to require somewhat that is declared as unused. – Sap Jul 10 '14 at 14:26
  • By the way do u know any good pattern to use if I need a large non-copyable class to be initialized in array? I've got a class with vector of unique_ptr. How can I initilize it in array? The class is pretty large and I really do not want to write a copying/moving constructor for it. – Sap Jul 10 '14 at 14:27
  • @user3544995 A vector of `unique_ptr`s should be movable. – T.C. Jul 10 '14 at 14:34
  • Look at the second example in my question - there is a class with unique_ptr. And an array of that class can not be initialized (because unique_ptr has a deleted copying constructor). Is there any idea how it can be done without copying constructor for the whole class (it is big for example)? – Sap Jul 10 '14 at 14:38
  • @user3544995 It [works for me](http://coliru.stacked-crooked.com/a/a1010a52bbd2fb04) modulo the missing semicolons at the end of class definitions. – T.C. Jul 10 '14 at 15:32
  • @user3544995 Are you using Visual Studio? I know that VC++ didn't do implicit move special member functions before the Nov 2013 CTP, so that might explain why your code didn't compile. – T.C. Jul 10 '14 at 16:54
  • @T.C. : I believe your assumption about VC++ is correct – VC++ 2013 neither automatically generates special move members, nor supports in-line initialization of NSDM arrays. – ildjarn Jul 10 '14 at 17:51