The convenient initializer_list
syntax seems to come at a price of not being able to move members of the list, creating unnecessary copies.
struct A
{
// some members which are dynamic resources...
A() { cout << "Default Constructor\n"; }
A(const A& original) { cout << "Copy constructor\n"; }
A(A&& original) { cout << "Move constructor\n"; }
};
int main() {
vector<A> v1{ A() , A() }; // calls copy
vector<A> v2;
v2.push_back(A()); v2.push_back(A()); // calls move
return 0;
}
If I understand correctly, this is because de-referencing the initializer iterators gives const T
, which will be copied even when move is attempted.
Is there a workaround for this?
Reading https://mcmap.net/q/179830/-initializer_list-and-move-semantics, a solution is proposed which uses variable argument templates, as follows:
template<class Array> struct maker;
// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
using result_type = std::vector<T, A>;
template<class...Ts>
auto operator()(Ts&&...ts) const -> result_type
{
result_type result;
result.reserve(sizeof...(Ts));
using expand = int[];
void(expand {
0,
(result.push_back(std::forward<Ts>(ts)),0)...
});
return result;
}
};
// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
using result_type = std::array<T, N>;
template<class...Ts>
auto operator()(Ts&&...ts) const
{
return result_type { std::forward<Ts>(ts)... };
}
};
//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
auto m = maker<Array>();
return m(std::forward<Ts>(ts)...);
}
(
aside1: this declares an array , will it be optimized away?
aside2: what is the meaning and purpose of void
in the expression? it seems unnecessary
)
With new changes in C++20 and C++23, is there now a better workaround for all this?
const T
" - that is because the underlying array that theinitializer_list
holds is aconst T[]
array. You can't moveconst
objects, only copy them. – Inductancepush_back
seems less than ideal, but the next best alternative seems to be to construct astd::array<T,N>
and then move from that to thevector
, which doesn't seem a whole lot better. – Scendranges::generate_n
? – Ballsvoid(expand{...});
statement that is part of theoperator()
method appears to be an unnamed function declaration returningvoid
and taking anint[]
parameter. The expression(result.push_back(std::forward<Ts>(ts)),0)
uses a comma operator to execute the push_back before returning a0
from the expression. – Kuhlvoid
of an array prvalue, not a function declaration. – Equipollent=
([dcl.fct]/3). – Equipollentvoid(expand { 0, (result.push_back(std::forward<Ts>(ts)),0)... });
is big brother of a call statement, An empty statement would be equivalent tovoid(0);
, a non-assigning function call statement would bevoid(function(arguments));
Here we havevoid(type{initializer-list});
which is meant to create statically unfolded loop based on that fold-expression inside of initializer – Floorer0,
before fold – Floorer