Possible Duplicate:
Can I list-initialize a vector of move-only type?
Edit 1: Please consider a re-open vote: My question emphasize in-place construction. Move construction is an alternative but not what this questions is about. Thanks for the answers!
Edit 2: Since I can't answer this question (It got closed) I post my own suggestion here. The following is not as good as the answers I accepted, but may be useful for others. At least only the move constructor is called:
std::vector<A2> vec;
{
std::array<A2,3> numbers{{{2,3},{5,6},{7,8}}};
vec.reserve(numbers.size());
for (auto &v: numbers) vec.emplace_back(std::move(v)) ;
}
Original post:
When thinking about the answer to this question: Initialization of classes within an STL array of vectors I found that I could not find a way to get in-place construction of the vector from an initialization list. What am I missing?
Now trying to be more clear, I would like this (perfectly correct) initialization
std::vector<A2> k{{2,3},{4,5},{8,9}};
to have an effect more similar to this:
std::vector<A2> k2;
k2.reserve(3);
k2.emplace_back(2,3);
k2.emplace_back(4,5);
k2.emplace_back(8,9);
However, in the first case the copy constructor is called for A2 on a temporary while inserting. Is there a way to avoid that? What does the standard say?
I desperately tried
std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};
but that generates an additional call to the move constructor, something I also did not expect. I just wanted to explicitly hint that A2 is a temporary, something I had thought was implied.
Full example:
#include <vector>
#include <iostream>
struct A2 {
int mk;
int mj;
A2(int k,int j) : mk(k),mj(j) {
std::cout << " constr for "<<this<< ":"<< mk<<std::endl;
}
A2(const A2& a2) {
mk=a2.mk;
mj=a2.mj;
std::cout << "copy constr for "<<this<< ":" << mk<<std::endl;
}
A2(A2&& a2) noexcept {
mk=std::move(a2.mk);
mj=std::move(a2.mj);
std::cout << "move constr for "<<this<< ":"<< mk<<std::endl;
}
};
struct Ano {
Ano() {
std::cout << " constr for "<<this <<std::endl;
}
Ano(const Ano& ano) {
std::cout << "copy constr for "<<this<<std::endl;
}
Ano(Ano&& ano) noexcept {
std::cout << "move constr for "<<this<<std::endl;
}
};
int main (){
// here both constructor and copy constructor is called:
std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};
std::cout << "......"<<std::endl;
std::vector<A2> k2;
k2.reserve(3);
// here (naturally) only constructor is called:
k2.emplace_back(2,3);
k2.emplace_back(4,5);
k2.emplace_back(8,9);
std::cout << "......"<<std::endl;
// here only constructor is called:
std::vector<Ano> anos(3);
}
Output:
constr for 0xbf9fdf18:2
constr for 0xbf9fdf20:4
constr for 0xbf9fdf0c:8
move constr for 0xbf9fdf28:8
copy constr for 0x90ed008:2
copy constr for 0x90ed010:4
copy constr for 0x90ed018:8
......
constr for 0x90ed028:2
constr for 0x90ed030:4
constr for 0x90ed038:8
......
constr for 0x90ed048
constr for 0x90ed049
constr for 0x90ed04a
List-initializing std::vector
in your snippet is no different from doing the following (if initializer_list
had a public non-explicit constructor or std::vector
accepted an array reference.):
// directly construct with the backing array of 'initializer_list'
std::vector<A2> v(alias<A2[]>{ A2(2,3), A2(4,5), A2(8,9) });
It's not a special way to construct a std::vector
that could take advantage of the implementation, really. List-initialization is a general-purpose way of "uniformly" initializing types. As such, there's no way it could tread std::vector
any different than any other user-defined type. As such, having the construct in the OP do emplace construction is out of question.
Now, the backing array (or any constant array) may be put in read-only memory by the implementation, that's the reason why
std::initializer_list<T>::iterator
is just
typedef T const* iterator;
So moving out of std::initializer_list
is also out of question.
Now, is there a solution? Yes, there is, and it's a rather easy one, actually!
We will want to have a free function that takes a container and a number of tuples equal to the number of elements you want to emplace. The tuples container the arguments to the constructor of the container type. Easy in theory, easy in practice with the indices trick (where indices == seq
and build_indices == gen_seq
in the code):
#include <type_traits>
#include <tuple>
#include <utility>
template<class T> using alias = T;
template<class T> using RemoveRef = typename std::remove_reference<T>::type;
template<class C, unsigned... Is, class Tuple>
void emplace_back_one(C& c, seq<Is...>, Tuple&& ts){
c.emplace_back(std::get<Is>(std::forward<Tuple>(ts))...);
}
template<class T> using Size = std::tuple_size<RemoveRef<T>>;
template<class C, class... Tuples>
void emplace_back(C& c, Tuples&&... ts){
c.reserve(sizeof...(Tuples));
alias<char[]>{(
emplace_back_one(c, gen_seq<std::tuple_size<RemoveRef<Tuples>>::value>{}, std::forward<Tuples>(ts))
, '0')...};
}
Live example with the implementation of seq
and gen_seq
.
The code above calls emplace_back_one
exactly sizeof...(Tuples)
times, passing one tuple at a time in the order that the were passed to emplace_back
. This code is also sequenced left-to-right, meaning that the constructors get called in the same order that you passed the tuples for them. emplace_back_one
then simply unpacks the tuple with the indices trick and passes the arguments to c.emplace_back
.
Construction of an object via std::initializer_list
is no different than constructing an object from any other object. The std::initializer_list
is not a mystical, phantasmal construct; it is a living, breathing C++ object (albeit a temporary one). As such, it obeys all the rules of regular living, breathing C++ objects.
Aggregate initialization can effectively elide the copy/moves because it's aggregate initialization, a purely compile-time construct. std::vector
is many things; an aggregate and a purely compile-time construct are not among them. So, in order for it to initialize itself from what it is given, it must execute actual C++ code, not compile-time stuff. It must iterate over each element of the initializer_list
and either copy those values or move them. And the latter is not possible, since std::initializer_list
doesn't provide non-const
access to its members.
Initializer list initialization is meant to look like aggregate initialization, not perform like it. That's the cost of having a runtime, dynamic abstraction like std::vector
.