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.
A
. Interestingly, clang calls the move ctor only once. – CalcicoleA
are created. – Cossvector<A> aa; aa.push_back(A()); B b(aa);
. Output:A::A A::A(A&&) A::~A A::A(A&)
– Strontianmain
finishes. I'm guessing that you usedcin.get()
or a breakpoint or something similar to look at the output, so they haven't gotten a chance to run. – CalcicoleB
's constructor, you'll need to either provide an overload takingvector<A> &&
or take the vector by value and do: va(std::move(va))
. If you take a const ref, it's always going to copy. – Calcicoleconst initializer_list<A>&
. If you have an idea how to get rid of the last remaining copy-operation then please let me know. Would be nice to move the initializer list right intoB.va
without copying anything. – Cossinitializer_list
, because there's no way to move from its elements. You'll have to manually populate the vector (with either the rvalue reference overload ofpush_back
or simplyemplace_back
) and thenstd::move
that vector toB
's constructor. – Calcicole