使用带有std :: fill等算法的emplace
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用带有std :: fill等算法的emplace相关的知识,希望对你有一定的参考价值。
我使用了vector::emplace_back
以避免在填充矢量时构造时间对象。这里有一个简化版本:
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);
但我想用std::fill_n
代替:
v.reserve(10);
std::fill_n(std::back_inserter(v), 10, Foo(1, 1.0));
通过这种方式,将创建临时副本。在这种情况下我不知道如何使用emplace
。我想我需要像std::back_emplacer
这样的东西,但我找不到这样的东西。这是C ++ 11的一部分,但还没有在GCC中实现?如果它不是C ++ 11的一部分,还有其他方法吗?
通常使用元组来简化传递可变数量的项目(在这种情况下,参数转发到emplace_back
),使用a little technique将元组解包回来。因此,可以通过要求用户使用元组工厂函数(back_emplacer
,std::make_tuple
,std::tie
之一)来编写std::forward_as_tuple
实用程序,这是有意义的:
#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 }; }
代码的演示是available。在你的情况下你想要调用std::fill_n(back_emplacer(v), 10, std::forward_as_tuple(1, 1.0));
(std::make_tuple
也可以接受)。你还需要通常的迭代器来完成这个功能 - 我推荐使用Boost.Iterators。
但我必须强调的是,当与std::fill_n
一起使用时,这样的实用程序并没有带来太多。在你的情况下,它将保存临时Foo
的构造,有利于一个参考元组(如果你使用std::make_tuple
值的元组)。我让它留给读者找到一些其他算法,其中back_emplacer
将是有用的。
你是对的,标准中没有back_emplacer
。你可以完全自己写一个,但是为了什么?
当你调用emplace_back
时,你必须提供构造函数(任何构造函数)的参数:例如vec.emplace_back(1, 2)
。但是,您不能随意在C ++中传递参数元组,因此back_emplacer
将仅限于一元构造函数。
在fill_n
的情况下,您提供了一个将被复制的参数,然后back_inserter
和back_emplacer
将使用相同的参数调用相同的复制构造函数。
请注意,有generate
和generate_n
算法来构建新元素。但同样,任何临时副本都可能被省略。
因此,我认为对back_emplacer
的需求相当轻,主要是因为语言不支持多个返回值。
编辑
如果你看下面的评论,你会发现使用std::forward_as_tuple
和std::is_constructible
的组合可以编写一个back_emplacer
机制。感谢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允许将函数的返回值直接省略到将要存储的位置。
虽然逻辑上是临时创建,但实际上并没有创建临时。并且您可以访问周围范围中的所有变量来决定如何创建元素,而不仅仅是常量,如果需要的话。
不会有任何“临时副本”。只有一个临时的,你传递给fill_n
的那个。它将被复制到每个值中。
即使有一个back_emplacer
,你会怎么称呼它?函数的emplace
家族采用构造函数参数; fill_n
将一个对象复制到迭代器中。
我已经看到@ LucDanton上面的回答(https://stackoverflow.com/a/12131700/1032917),我仍然看不到使代码过于复杂的问题(除了它在2012年写回来的事实,但即使考虑到......)。无论如何,我发现以下代码与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};
}
使用C ++ 17中的CTAD,您甚至可以摆脱back_emplacer
并编写back_emplace_iterator(my_container)
而无需显式提供模板参数。
我最近向愚蠢的库提交了一个emplace_iterator
类和相关的实用程序函数。我相信它解决了原始问题并支持自动解压缩传递给std::tuple
的operator=
参数。
编辑:更新链接: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
类似于std::make_tuple
,但导致其参数完美转发给Widget
构造函数。 (std::make_tuple
和类似的可能导致额外的副本,并且不保留左值与右值类型。)在这个具体的例子中,使用std::make_tuple
会产生相同的效果。
以上是关于使用带有std :: fill等算法的emplace的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 std::optional<T>::emplace 的第二个重载
std::emplace_back 调用向量中其他对象的构造函数?
std::vector::emplace_back 比 std::vector::push_back 慢的示例?
C ++:堆栈的 push() 与 emplace() [重复]
std::vector emplace_back 可以从向量本身的元素复制构造吗?
即使根据容量()仍有未使用的空间,std::vector 能否将其数据移动到 emplace_back()处的另一个地址?