Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I emplace multiple elements in a sequence?

C++11 has introduced emplace function to construct an element in-place inside a sequence. This is complementary to insert which either copies or moves elements.

However, out of several overloads of insert, only the single element insert version,

i.e.

iterator insert( const_iterator p, T const& x);
iterator insert( const_iterator p, T&& x );

has an emplace version,

template< class... Args > 
iterator emplace(const_iterator p, Args&&... x);

Is there any reason, not to allow construction of n elements in-place using emplace?

While a overload like,

template< class... Args > 
iterator emplace(const_iterator p,size_type n,Args&&... x);

just like the corresponding insert

iterator insert(const_iterator p,size_type n,const_reference x);

may conflict with the other overload, taking the constructor arguments as a tuple, or using some special tag like in_place_t likely to disambiguate them.

EDIT The proposed function emplace_n for vector may have behaviour like the one given below

template<class... Args>
iterator emplace_n(const_iterator p,size_type n,Args&&... x)
{
    size_type const offset = p - begin();
    if(capacity() < size()+n)
    {
        vector<T> v;
        v.reserve(size()+n);
        v.assign(make_move_iterator(begin(),make_move_iterator(end());
        swap(*this,v);
    }
    auto const sz = size();
    for(auto c = 0; c != n ; ++c)
    {
        emplace_back(x...); //can do forward only if n == 1
    }
    rotate(begin()+offset,begin()+sz,end());
    return iterator{begin() + offset};
}
like image 677
abir Avatar asked Oct 21 '22 13:10

abir


1 Answers

The problem is that there is no easy way to determine when the arguments for one element end and the arguments for the next begin. You can pass the arguments via tuples though like for pair's piecewise constructor, and end up with a helper function like this:

template<int... Is>
struct index_seq { };

template<int N, int... Is>
struct make_index_seq : make_index_seq<N - 1, N - 1, Is...> { };

template<int... Is>
struct make_index_seq<0, Is...> : index_seq<Is...> { };

template<class Cont, class Tup, int... Is>
void emplace_back_impl(Cont& c, Tup&& tup, index_seq<Is...>)
{
    using std::get;
    c.emplace_back(get<Is>(std::forward<Tup>(tup))...);
}

template<class Cont, class... Tups>
void emplace_multiple(Cont& c, Tups&&... tups)
{
    int const unpack[]{
        0, ((emplace_back_impl)(c, std::forward<Tups>(tups),
                                make_index_seq<
                                    std::tuple_size<typename std::remove_reference<Tups>::type>{}
                                >()), 0)...
    };
    static_cast<void>(unpack);
}

Then you can use the emplace_multiple function to emplace multiple elements:

std::vector<std::string> xs;
emplace_multiple(xs, std::make_tuple("Hello, world!"), std::make_tuple(10, 'a'));

Note that this uses emplace_back. Using emplace plus an iterator to mark the insertion position is dangerous if you haven't reserved enough space in advance. This is because emplace and emplace_back can invalidate iterators (at least for vectors), and then the function doesn't know how to get a new iterator to the position you intended.

Demo here.

like image 176
Simple Avatar answered Oct 23 '22 04:10

Simple