【问题标题】:Using emplace with algorithms such as std::fill将 emplace 与 std::fill 等算法一起使用
【发布时间】:2012-08-26 11:32:01
【问题描述】:

我使用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 的一部分,还有其他方法吗?

【问题讨论】:

    标签: c++ stl c++11 stl-algorithm


    【解决方案1】:

    通常使用元组来简化传递可变数量的项目(在这种情况下,将参数转发到emplace_back),使用a little technique 将元组解包回来。因此,可以通过要求用户在有意义的地方使用元组工厂函数(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 }; }
    

    代码演示是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 有用的其他算法。

    【讨论】:

    • 这太神奇了:) 正如 Matthieu 和 Nicol 所指出的,只需要构造一个对象。所以这可能不是什么大问题。但是,我接受了这个答案,因为它实际上提供了一个可行的解决方案。谢谢!
    【解决方案2】:

    你说得对,标准中没有back_emplacer。你完全可以自己写一个,但是有什么用呢?

    当您调用emplace_back 时,您必须为构造函数(任何构造函数)提供参数:例如vec.emplace_back(1, 2)。但是,您不能在 C++ 中任意传递参数元组,因此 back_emplacer 将仅限于一元构造函数。

    fill_n 的情况下,您提供一个将被复制的参数,然后back_inserterback_emplacer 将使用相同的参数调用相同的复制构造函数。 p>

    请注意,有 generategenerate_n 算法来构建新元素。但同样,任何临时副本都可能会被忽略。

    因此,我认为back_emplacer 的需求相当轻,主要是因为该语言不支持多个返回值。

    编辑

    如果您查看下面的 cmets,您会意识到使用 std::forward_as_tuplestd::is_constructible 的组合可以编写 back_emplacer 机制。感谢 Luc Danton 的突破。

    【讨论】:

      【解决方案3】:
      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 允许将函数的返回值直接省略到将要存储的位置。

      虽然在逻辑上创建了一个临时的,但实际上并没有创建一个临时的。如果需要,您可以访问周围范围内的所有变量来决定如何创建元素,而不仅仅是常量。

      【讨论】:

        【解决方案4】:

        不会制作任何“临时副本”。将有一个临时的,即您传递给fill_n 的那个。并将其复制到每个值中。

        即使有back_emplacer,你会用什么称呼它? emplace 系列函数采用构造函数参数; fill_n 将一个 object 复制到迭代器中。

        【讨论】:

          【解决方案5】:

          我在上面看到了@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) 而无需显式提供模板参数。

          【讨论】:

            【解决方案6】:

            我最近向 folly 库提交了一个 emplace_iterator 类和相关的实用函数。我相信它解决了原始问题并支持自动解压缩传递给operator=std::tuple 参数。

            编辑:更新链接: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 将具有相同的效果。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-09-29
              • 1970-01-01
              • 2015-06-21
              • 2014-08-29
              • 1970-01-01
              相关资源
              最近更新 更多