c++c++14operator-precedencec++17construction

Utilizing move-semantics and element-wise initialization of containers


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)?


Solution

  • There's a better solution:

    template<class Container, std::size_t N>
    inline Container make_container(typename Container::value_type (&&a)[N])
    {
        return Container(std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)));
    }
    

    You can use it this way:

    make_container<std::vector<A>>({A(1), A(2)})
    

    It doesn't need variadic template and it's still list-initialization but not std::initializer_list, this time it's a plain array so you can move the elems from it.

    Notable benefits compared to your original solution:

    DEMO