What happens technically in this C++ code?

c++, c++11

Solution

It might be helpful to go through the constructor calls in reverse order.

B b({ A() });

To construct a `B`, the compiler must call B's constructor that takes a `const vector<A>&`. That constructor in turn must make a copy of the vector, including all of its elements. That's the second copy ctor call you see.

To construct the temporary vector to be passed to `B`'s constructor, the compiler must invoke the `initializer_list` constructor of `std::vector`. That constructor, in turn, must make a copy of what's contained in the `initializer_list`*. That's the first copy constructor call you see.

The standard specifies how `initializer_list` objects are constructed in §8.5.4 [dcl.init.list]/p5:

An object of type `std::initializer_list<E>` is constructed from an initializer list as if the implementation allocated an array of N elements of type `const E`**, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the `std::initializer_list<E>` object is constructed to refer to that array.

Copy-initialization of an object from something of the same type uses overload resolution to select the constructor to use (§8.5 [dcl.init]/p17), so with an rvalue of the same type it will invoke the move constructor if one is available. Thus, to construct the `initializer_list<A>` from the braced initializer list, the compiler will first construct an array of one `const A` by moving from the temporary `A` constructed by `A()`, causing a move constructor call, and then construct the `initializer_list` object to refer to that array.

I can't figure out where the other move in g++ comes from, though. `initializer_list`s are usually nothing more than a pair of pointers, and the standard mandates that copying one doesn't copy the underlying elements. g++ seems to call the move constructor twice when creating an `initializer_list` from a temporary. It even calls the move constructor when constructing an `initializer_list` from a lvalue.

My best guess is that it's implementing the standard's non-normative example literally. The standard provides the following example:

struct X {
    X(std::initializer_list<double> v);
};

X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:**

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

assuming that the implementation can construct an initializer_list object with a pair of pointers.

So if you take this example literally, the array underlying the `initializer_list` in our case will be constructed as if by:

const A __a[1] = { A{A()} };

which does incur two move constructor calls because it constructs a temporary `A`, copy-initializes a second temporary `A` from the first one, then copy-initializes the array member from the second temporary. The normative text of the standard, however, makes clear that there should only be one copy-initialization, not two, so this seems like a bug.

Finally, the first `A::A` comes directly from `A()`.

There's not much to discuss about the destructor calls. All temporaries (regardless of number) created during the construction of `b` will be destructed at the end of the statement in reverse order of construction, and the one `A` stored in `b` will be destructed when `b` goes out of scope.

* The `initializer_list` constructors of standard library containers are defined as being equivalent to invoking the constructor taking two iterators with `list.begin()` and `list.end()`. Those member functions return a `const T*`, so it can't be moved from. In C++14, the backing array is made `const`, so it's even clearer that you can't possibly move from it or otherwise change it.

** This answer originally quoted N3337 (the C++11 standard plus some minor editorial changes), which has the array having elements of type `E` rather than `const E` and the array in the example being of type `double`. In C++14, the underlying array was made `const` as a result of CWG 1418.

Problem

I have a class `B` which contains a vector of class `A`. I want to initialize this vector through the constructor. Class `A` outputs some debug info so I can see when it is constructed, destructed, copied or moved. ``` #include <vector> #include <iostream> using namespace std; class A { public: A() { cout << "A::A" << endl; } ~A() { cout << "A::~A" << endl; } A(const A& t) { cout <<"A::A(A&)" << endl; } A(A&& t) { cout << "A::A(A&&)" << endl; } }; class B { public: vector<A> va; B(const vector<A>& va) : va(va) {}; }; int main(void) { B b({ A() }); return 0; } ``` Now when I run this program (Compiled with GCC option `-fno-elide-constructors` so the move constructor calls are not optimized away) I get the following output: ``` A::A A::A(A&&) A::A(A&&) A::A(A&) A::A(A&) A::~A A::~A A::~A A::~A A::~A ``` So instead of just one instance of `A` the compiler generates five instances of it. `A` is moved two times and it is copied two times. I didn't expect that. The vector is passed by reference to the constructor and then copied into the class field. So I would have expected a single copy-operation or even just a move operation (because I hoped the vector I pass to the constructor is just a rvalue), not two copies and two moves. Can someone please explain what exactly happens in this code? Where and why does it create all these copies of `A`?

Original source