Using emplace with algorithms such as std::fill
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.
You are right that there is no back_emplacer
in the standard. You could perfectly write one yourself, but what for ?
When you call emplace_back
, you have to provide the arguments for the constructor (any constructor): vec.emplace_back(1, 2)
for example. However, you cannot arbitrarily pass tuples of arguments in C++, so the back_emplacer
would be limited to unary constructor.
In the case of fill_n
, you provide an argument that will be copied, and then both back_inserter
and back_emplacer
would call the same copy constructor with the same argument.
Note that there is the generate
and generate_n
algorithms to build new elements. But likewise any temporary copy will probably be elided.
Therefore I think the need for a back_emplacer
is rather light, mostly because of the language non-support of multiple return values.
EDIT
If you look at the comments below you will realize that using a combination std::forward_as_tuple
and std::is_constructible
it could be possible to write a back_emplacer
mechanism. Thanks to Luc Danton for the breakthrough.
class Foo {
public:
Foo(int i, double d) : i_(i), d_(d) {}
};
std::vector<Foo> v;
v.reserve(10);
std::generate_n(std::back_inserter(v), 10, [&]()->Foo{ return {1, 1.0}; });
RVO allows the return value of a function to be elided directly into where it is going to be stored.
While logically a temporary is created, in actual fact no temporary is created. And you have access to all variables in the surrounding scope to decide how to create the element, not just constants, if you want them.