简体   繁体   English

使用带有std :: fill等算法的emplace

[英]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_tuplestd::tiestd::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_inserterback_emplacer将使用相同的参数调用相同的复制构造函数。

Note that there is the generate and generate_n algorithms to build new elements. 请注意,有generategenerate_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_tuplestd::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_tuplestd::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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM