Often seen that examples of using STL algorithms are illustrated with list-initialized containers like:
std::vector< int > v{1, 2, 3, 4};
But when this approach is used for (heavyweight) classes (unlike ints) it implies excessive copy operations of ones, even if they are passed by rvalue (moved to), because std::initializer_list used in the example above provides only const_iterators.
To solve this problem I use the following (C++17) approach:
template< typename Container, typename ...Args >
Container make_container(Args &&... args)
{
Container c;
(c.push_back(std::forward< Args >(args)), ...);
// ((c.insert(std::cend(c), std::forward< Args >(args)), void(0)), ...); // more generic approach
return c;
}
auto u = make_container< std::vector< A > >(A{}, A{}, A{});
But it becames unsatisfactory when I do the following:
A a;
B b;
using P = std::pair< A, B >;
auto v = make_container< std::vector< P > >(P{a, b}, P{std::move(a), std::move(b)});
Here I want to save one copy operation per value by means of a replace a copy operation by a move operation (assume, to move A or B is much cheaper then to copy), but generally can't, because order of evaluation of function arguments is undefined in C++. My current solution is:
template< Container >
struct make_container
{
template< typename ...Args >
make_container(Args &&... args)
{
(c.push_back(std::forward< Args >(args)), ...);
}
operator Container () && { return std::move(c); }
private :
Container c;
};
A a; B b;
using P = std::pair< A, B >;
using V = std::vector< P >;
V w = make_container< V >{P{a, b}, P{std::move(a), std::move(b)}};
It is often considered as a bad practice to make some non-trivial work in bodies of constructors, but here I intensively used the very property of list-initialization — the fact that it is strictly left-to-right ordered.
Is it totally wrong approach from some particular point of view? What are the downsides of this approach besides the one mentioned above? Is there another technique to achieve predictable order of evaluation of function arguments currently (in C++11, C++14, C++1z)?
make_container< std::vector< A > >(a, std::move(b), std::move(a));is fine. Remember thatstd::moveis a cast, it does not move anything. Your function will receiveA&,B&&, andA&&. If your function strictly moves out of arguments in that same order there is no problem.operator ,is sequenced (here forvoidresult type ofpush_backfor STL containers it does and can't be overloaded). What if is not? Say, the body ofmakefunction can use arguments in any order (oroperator ,is eventually overloaded for some use case).make_containermight push in reverse for example, then there is no possible solution (other than creating a temporary for every element), it seems to me