矢量的元组和推回

Tuple of vectors and push back

本文关键字:元组      更新时间:2023-10-16

我有一个向量元组,我想将初始化器列表中的每个值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 ]
// }

这可能并不明显,但在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()(这是一个简短的函数),然后您需要使用扩展器技巧而不是使用fold表达式:

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,除了我们不能使用通用lambdas,并且实现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);
}
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...);
}

魔杖盒示例


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

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)>());
};

魔杖盒示例

C++14解决方案,从C++17向下实现apply

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";
}

实例

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

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<0>index_t<N-1>调用它。

for_each_arg接受一个可调用函数,并返回一个接受任意数量参数的lambda。它依次用这些参数中的每一个调用callable。

我们把Fields...fields拼接在一起,构建一个index_over,为它提供一组编译时的索引。然后我们将这些字段存储在r和l值引用的tuple中。

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

有些编译器不允许非constexprstd::integral_constantconstexpr上下文中转换为标量。它们是错误的和/或过时的。对于这些,您必须执行decltype(Is)::value