[英]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. 我使用了vector::emplace_back
以避免在填充向量时构造时态对象。 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: 但我想使用std::fill_n
代替:
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. 在这种情况下,我不知道如何使用emplace
。 I guess I would need something like std::back_emplacer
, but I could not find such a thing. 我想我需要像std::back_emplacer
这样的东西,但我找不到这样的东西。 Is that part of C++11, but not implemented in GCC yet? 这是C ++ 11的一部分,但还没有在GCC中实现? If it is not part of C++11, is there any other way to do that? 如果它不是C ++ 11的一部分,还有其他方法吗?
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. 通常使用元组来简化传递可变数量的项目(在这种情况下,参数转发到emplace_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: 因此,可以通过要求用户使用元组工厂函数( std::make_tuple
, std::tie
, std::forward_as_tuple
)来编写back_emplacer
实用程序,这是有意义的:
#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::fill_n(back_emplacer(v), 10, std::forward_as_tuple(1, 1.0));
( std::make_tuple
is also acceptable). ( std::make_tuple
也可以接受)。 You'd also want the usual iterator stuff to make the feature complete -- I recommend Boost.Iterators for that. 你还需要通常的迭代器来完成这个功能 - 我推荐使用Boost.Iterators。
I must really stress however that such a utility doesn't bring much when used with std::fill_n
. 我必须强调的是,当与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
). 在你的情况下,它将保存临时Foo
的构造,支持一个引用元组(如果你使用std::make_tuple
元组值)。 I leave it to the reader to find some other algorithm where back_emplacer
would be useful. 我留给读者去寻找一些其他算法,其中back_emplacer
会很有用。
You are right that there is no back_emplacer
in the standard. 你是对的,标准中没有back_emplacer
。 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. 当你调用emplace_back
,你必须提供构造函数(任何构造函数)的参数: vec.emplace_back(1, 2)
。 However, you cannot arbitrarily pass tuples of arguments in C++, so the back_emplacer
would be limited to unary constructor. 但是,您不能随意在C ++中传递参数元组,因此back_emplacer
将仅限于一元构造函数。
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. 在fill_n
的情况下,您提供了一个将被复制的参数,然后back_inserter
和back_emplacer
将使用相同的参数调用相同的复制构造函数。
Note that there is the generate
and generate_n
algorithms to build new elements. 请注意,有generate
和generate_n
算法来构建新元素。 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. 因此我认为对back_emplacer
的需求相当轻,主要是因为语言不支持多个返回值。
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. 如果你看下面的评论,你会发现使用std::forward_as_tuple
和std::is_constructible
的组合可以编写一个back_emplacer
机制。 Thanks to Luc Danton for the breakthrough. 感谢Luc Danton的突破。
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. RVO允许将函数的返回值直接省略到将要存储的位置。
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. 并且您可以访问周围范围中的所有变量来决定如何创建元素,而不仅仅是常量,如果需要的话。
There won't be any "temporal copies" made. 不会有任何“临时副本”。 There will be exactly one temporary, the one you passed to fill_n
. 将只有一个临时值,即您传递给fill_n
临时fill_n
。 And it will be copied into each value. 它将被复制到每个值中。
And even if there was a back_emplacer
, what would you call it with? 即使有一个back_emplacer
,你会怎么称呼它? The emplace
familiy of functions take constructor parameters; 函数的emplace
采用构造函数参数; fill_n
takes an object to copy into the iterator. fill_n
将一个对象复制到迭代器中。
I recently submitted an emplace_iterator
class and related utility function to the folly library. 我最近向folly库提交了一个emplace_iterator
类和相关的实用程序函数。 I believe it solves the original question and supports automatic unzipping of std::tuple
arguments passed to operator=
. 我相信它解决了原始问题并支持自动解压缩传递给operator=
的std::tuple
参数。
Edit: Updated Link: https://github.com/facebook/folly/blob/master/folly/container/Iterator.h 编辑:更新链接: https : //github.com/facebook/folly/blob/master/folly/container/Iterator.h
class Widget { Widget(int, int); };
std::vector<Widget> makeWidgets(const std::vector<int>& in) {
std::vector<Widget> out;
std::transform(
in.begin(),
in.end(),
folly::back_emplacer(out),
[](int i) { return folly::make_emplace_args(i, i); });
return out;
}
folly::make_emplace_args
is analogous to std::make_tuple
but results in perfect forwarding of its arguments to the Widget
constructor. folly::make_emplace_args
类似于std::make_tuple
但std::make_tuple
其参数完美转发给Widget
构造函数。 ( std::make_tuple
and similar may result in additional copies and does not preserve lvalue vs rvalue typedness.) In this specific example, using std::make_tuple
would have the same effect though. ( std::make_tuple
和类似的可能会导致额外的副本,并且不会保留左值与右值类型。)在这个具体示例中,使用std::make_tuple
会产生相同的效果。
I've seen @LucDanton's answer above ( https://stackoverflow.com/a/12131700/1032917 ) and I still cannot see the point of making the code overly complicated (apart from the fact it was written back in 2012, but even given that...). 我已经看到@ LucDanton上面的答案( https://stackoverflow.com/a/12131700/1032917 ),我仍然看不出使代码过于复杂的问题(除了它在2012年写回来的事实,但即便如此)鉴于...)。 Anyway, I find the following code as functional as Luc's: 无论如何,我发现以下代码与Luc的功能相同:
template <typename Container>
class back_emplace_iterator
{
public:
explicit back_emplace_iterator(Container & container)
: container(std::addressof(container))
{}
template <typename... Args>
back_emplace_iterator & operator=(Args &&... args)
{
static_assert(std::is_constructible_v<typename Container::value_type, Args...>, "should be constructible");
assert(container);
container->emplace_back(std::forward<Args>(args)...);
return *this;
}
// 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};
}
And with CTAD in C++17, you can even get rid of back_emplacer
and write back_emplace_iterator(my_container)
without explicitly giving the template arguments. 使用C ++ 17中的back_emplacer
,您甚至可以摆脱back_emplacer
并编写back_emplace_iterator(my_container)
而无需显式提供模板参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.