Search code examples
c++stlc++11stl-algorithm

Using emplace with algorithms such as std::fill


I have used vector::emplace_back in order to avoid constructing temporal objects while filling a vector. Here you have a simplified version:

class Foo {
public:
    Foo(int i, double d) : i_(i), d_(d) {}
    /* ... */
};

std::vector<Foo> v;
v.reserve(10);
for (int i = 0; i < 10; i++)
    v.emplace_back(1, 1.0);

But I wanted to use std::fill_n instead:

v.reserve(10);
std::fill_n(std::back_inserter(v), 10, Foo(1, 1.0));

In this way, temporal copies will be created, though. I do not know how to use emplace in this situation. I guess I would need something like std::back_emplacer, but I could not find such a thing. Is that part of C++11, but not implemented in GCC yet? If it is not part of C++11, is there any other way to do that?


Solution

  • It's common to use tuples to ease the pass a variadic number of items (in this case, parameters to forward to emplace_back), with a little technique to unpack the tuple back. As such it is possible to write a back_emplacer utility by requiring the user to make use of the tuple factory functions (one of std::make_tuple, std::tie, std::forward_as_tuple) where it make sense:

    #include <type_traits>
    #include <tuple>
    
    // Reusable utilites
    
    template<typename T>
    using RemoveReference = typename std::remove_reference<T>::type;
    template<typename T>
    using Bare = typename std::remove_cv<RemoveReference<T>>::type;
    
    template<typename Out, typename In>
    using WithValueCategoryOf = typename std::conditional<
        std::is_lvalue_reference<In>::value
        ,  typename std::add_lvalue_reference<Out>::type
        , typename std::conditional<
            std::is_rvalue_reference<Out>::value
            , typename std::add_rvalue_reference<Out>::type
            , Out
        >::type
    >::type;
    
    template<int N, typename Tuple>
    using TupleElement = WithValueCategoryOf<
        typename std::tuple_element<N, RemoveReference<Tuple>>::type
        , Tuple
    >;  
    
    // Utilities to unpack a tuple
    template<int... N>
    struct indices {
        using next = indices<N..., sizeof...(N)>;
    };
    
    template<int N>
    struct build_indices {
        using type = typename build_indices<N - 1>::type::next;
    };
    template<>
    struct build_indices<0> {
        using type = indices<>;
    };
    
    template<typename Tuple>
    constexpr
    typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
    make_indices() { return {}; }
    
    template<typename Container>
    class back_emplace_iterator {
    public:
        explicit back_emplace_iterator(Container& container)
            : container(&container)
        {}  
    
        template<
            typename Tuple
            // It's important that a member like operator= be constrained
            // in this case the constraint is delegated to emplace,
            // where it can more easily be expressed (by expanding the tuple)   
            , typename = decltype( emplace(std::declval<Tuple>(), make_indices<Tuple>()) )
        >
        back_emplace_iterator& operator=(Tuple&& tuple)
        {
            emplace(*container, std::forward<Tuple>(tuple), make_indices<Tuple>());
    
            return *this;
        }
    
        template<
            typename Tuple
            , int... Indices  
            , typename std::enable_if<
                std::is_constructible<
                    typename Container::value_type
                    , TupleElement<Indices, Tuple>...
                >::value
                , int
            >::type...
        >
        void emplace(Tuple&& tuple, indices<Indices...>)
        {
            using std::get;
            container->emplace_back(get<Indices>(std::forward<Tuple>(tuple))...);
        }
    
        // Mimic interface of std::back_insert_iterator
        back_emplace_iterator& operator*() { return *this; }
        back_emplace_iterator& operator++() { return *this; }
        back_emplace_iterator operator++(int) { return *this; }
    
    private:
        Container* container;  
    };
    
    template<typename Container>
    back_emplace_iterator<Container> back_emplacer(Container& c)
    { return back_emplace_iterator<Container> { c }; }
    

    A demonstration of the code is available. In your case you'd want to call std::fill_n(back_emplacer(v), 10, std::forward_as_tuple(1, 1.0)); (std::make_tuple is also acceptable). You'd also want the usual iterator stuff to make the feature complete -- I recommend Boost.Iterators for that.

    I must really stress however that such a utility doesn't bring much when used with std::fill_n. In your case it would save the construction of the temporary Foo, in favour of a tuple of references (a tuple of values if you were to use std::make_tuple). I leave it to the reader to find some other algorithm where back_emplacer would be useful.