【问题标题】:Tuple of vectors and push back向量元组和推回
【发布时间】:2017-01-29 20:21:32
【问题描述】:

我有一个向量元组,我想将初始化列表中的每个值push_back() 放入“向量元组”中的相应向量中。代码中的create() 函数是我想做的地方。

template<typename...Fields>
class ComponentManager
{
  using Index = int;
public:
  /**
   * Provides a handle to a component
   **/
  struct ComponentHandle {
    static constexpr Index Nil = -1;
    bool nil() { return index == Nil; }
    const Index index;
  };

  ComponentHandle lookup(Entity e) { 
    return ComponentHandle{get(m_map,e,-1)}; 
  }

  template<int i>
  auto get(ComponentHandle handle) {
    return std::get<i>(m_field)[handle.index];
  }

  ComponentHandle create(Entity e, Fields ...fields) {
    m_entity.push_back(e);
    // m_fields.push_back ... ???
  }

private:
  std::vector<Entity>                m_entity;
  std::tuple<std::vector<Fields>...> m_field;
  std::map<Entity,Index>             m_map;
};

例子:

Entity e1, e2;
ComponentManager<int,float,int> cm{};
cm.create(e1, 42, 1337.0, 99);
cm.create(e2, 84, 2674.0, 198);
// Resulting contents of cm.m_field tuple
// { 
//    vector<int>   [ 42, 84 ], 
//    vector<float> [ 1337.0, 2674.0 ]
//    vector<int>   [ 99, 198 ]
// }

【问题讨论】:

  • 你有一个向量的tuple,而不是元组的向量。您能否通过一些示例输入和所需输出来澄清您的问题?
  • 我修复了这个问题并澄清了它是一个初始化列表,而不是一个要从中检索值的元组

标签: c++ c++11 templates c++14


【解决方案1】:

这可能不是很明显,但在 C++ 中解包元组的方法是使用 std::apply()。使用 C++17,这很简单:

void create(Entity e, Fields ...fields) {
    m_entity.push_back(e);
    std::apply([&](auto&... vs) {
       (vs.push_back(fields), ...);
    }, m_field);
}

对于 C++14,我建议您自己实现 apply()(它是 short function),然后您需要使用 expander trick 而不是使用折叠表达式:

void create(Entity e, Fields ...fields) {
    m_entity.push_back(e);
    not_std::apply([&](auto&... vs) {
       using swallow = int[];
       (void)swallow{0, 
           (vs.push_back(fields), 0)...
           };
    }, m_field);
}

对于 C++11,大部分内容都适用,除了我们不能使用泛型 lambda 并且实现 std::apply 比在链接引用中更冗长(但并不复杂)。值得庆幸的是,除了缩短代码之外,我们实际上不需要做任何事情——我们知道所有的向量类型:

void create(Entity e, Fields ...fields) {
    m_entity.push_back(e);
    not_std::apply([&](std::vector<Fields>&... vs) {
       using swallow = int[];
       (void)swallow{0, 
           (vs.push_back(fields), 0)...
           };
    }, m_field);
}

【讨论】:

  • 什么,不喜欢index_over
  • @Yakk 什么,来自here?这不是运行时的事情吗?
  • nm,看起来我最终使用 index over 来实现 apply 来实现 for_each_tuple_element。
  • 不,我说得太早了。 for_each_tuple_element 需要apply,但您可以不使用apply 来解决OP 的问题。现在我看了你的回答,我很喜欢你如何解释vsfields 大小相同的事实,我没有想到这一点。
【解决方案2】:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
    (void) std::initializer_list<int>{(f(std::forward<Args>(args)), 0)...};
}

ComponentHandle create(Entity e, Fields ...fields) 
{
    for_each_argument([&](auto field)
                  { 
                      using field_type = std::vector<std::decay_t<decltype(field)>>;
                      std::get<field_type>(m_field).push_back(field); 
                  },
                  fields...);
}

wandbox example


我没有在问题中提到字段可以是相同类型的要求,因此例如元组中可以有多个向量。

template <typename TVec, typename TFieldTuple, std::size_t... TIdxs>
void expander(TVec& vec, TFieldTuple ft, std::index_sequence<TIdxs...>)
{
    for_each_argument([&](auto idx)
    {
        std::get<idx>(vec).push_back(std::get<idx>(ft));
    }, std::integral_constant<std::size_t, TIdxs>{}...);
}

const auto create = [](auto& vec, auto ...fields) 
{
    expander(vec, 
             std::make_tuple(fields...), 
             std::make_index_sequence<sizeof...(fields)>());
};

wandbox example

【讨论】:

  • 我没有在问题中提到字段可以是相同类型的要求,因此例如元组中可以有多个vector
  • 非常感谢,它就像一个魅力!我羡慕你的 C++ 知识
【解决方案3】:

从 C++17 中途实现 apply 的 C++14 解决方案。

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void,std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&&f)->decltype(auto) {
    return decltype(f)(f)( index<Is>... );
  };
}
template<std::size_t N>
auto index_over( index_t<N> ={} ) {
  return index_over( std::make_index_sequence<N>{} );
}


template<class F>
auto for_each_arg( F&& f ) {
  return [f = std::forward<F>(f)](auto&&...args)->decltype(auto) {
    using discard=int[];
    (void)discard{0,(void(
      f(decltype(args)(args))
    ),0)...};
  };
}

这些很有用,但不是必需的:

template<class F, class Tuple>
decltype(auto) apply( F&& f, Tuple&& tuple ) {
  auto count = index< std::tuple_size< std::decay_t<Tuple> >{} >;
  return index_over( count )( [&](auto...Is) {
    using std::get;
    return std::forward<F>(f)( get<decltype(Is)::value>( std::forward<Tuple>(tuple) )... );
  } );
}
template<class Tuple>
auto for_each_tuple_element(Tuple&& tuple) {
  return [&](auto&& f)->decltype(auto){
    return apply(
      for_each_arg( decltype(f)(f) ),
      std::forward<Tuple>(tuple)
    );
  };
}

测试代码:

int main() {
    std::tuple< std::vector<int>, std::vector<char> > tup;
    for_each_tuple_element( tup )( [](auto&& v) {
      v.push_back(3);
    });
    std::cout << std::get<0>(tup).size() << "," << std::get<1>(tup).size() << "\n";
}

live example

然后我们可以将其应用于您的问题。

ComponentHandle create(Entity e, Fields ...fields) {
  m_entity.push_back(e);
  auto indexer = index_over<sizeof...(fields>();
  auto fields_tuple = std::forward_as_tuple( std::forward<Fields>(fields)... );
  indexer( for_each_arg([&](auto Is){
    std::get<Is>(m_fields).push_back(
      std::get<Is>(decltype(fields_tuple)(fields_tuple))
    );
  } );
}

index_over 采用编译时 N 并返回一个 lambda。此 lambda 接受一个可调用对象,并使用 index_t&lt;0&gt;index_t&lt;N-1&gt; 调用它。

for_each_arg 接受一个可调用对象,并返回一个接受任意数量参数的 lambda。它依次使用每个参数调用可调用对象。

我们将Fields...fields 拼接在一起,构建一个index_over 以获取一组编译时索引。然后,我们将这些字段存储在由 r 和 l 值引用组成的 tuple 中。

我们在单个索引Is 上编写操作。然后我们将其传递给for_each_arg,并将返回值传递给index_over,并为每个索引调用单个索引处理lambda。

一些编译器不允许非constexpr std::integral_constantconstexpr 上下文中转换为标量。它们是错误的和/或过时的。对于那些,你必须做decltype(Is)::value

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    • 2011-09-22
    • 1970-01-01
    相关资源
    最近更新 更多