有没有一种方法可以使用相同的索引迭代 std::tuple 和 std::array?

5

我正在编写一个简单的实体组件系统框架,想要使用可变参数模板来获取更灵活的接口。对于每个组件,我都将偏移量(从块的内存开始)存储在std::array中。在我的"update()"方法中,我想要从该数组中读取偏移量,将其添加到块的指针上,并将指向特定组件的指针直接传递给lambda作为参数。我尝试使用std::index_sequence,但我无法同时将其作为元组和数组的索引。

    template<typename ...Cn>
    class SystemGroup {
        public:
            using OffsetArray = std::array<uint16_t, sizeof...(Cn)>;

            static constexpr size_t kMaxGroups = 16;
            static constexpr GroupIndex kInvalidIndex = -1;

            struct Group {
                static constexpr uint8_t kNumComponents = sizeof...(Cn);

                OffsetArray componentOffsets;
                Chunk *pFirstChunk;
            };
    };

    template<typename ...Cn>
    void SystemGroup<Cn...>::update() {
        for (auto group : m_groups) {
            // iterate over archetype's chunks
            ecs::Chunk *pChunk = group.pFirstChunk;
            do {
                // get component data
                std::tuple<Cn*...> pointers;
                
                // Here is the problem. I don't know how to iterate over tuple and array using variadic templates
                // pointers[0] = pChunk->memory + m_groups.componentOffsets[0];
                // pointers[1] = pChunk->memory + m_groups.componentOffsets[1];
                // pointers[sizeof..(Cn)] = pChunk->memory + m_groups.componentOffsets[sizeof..(Cn)];

                auto updateComponents = [](int *pIntegers, float *pFloats) {
                };
                std::apply(updateComponents, pointers);
                pChunk = pChunk->header.pNext;
            } while(pChunk);
        }
    }

编辑 感谢大家的帮助。我决定选择max66提出的解决方案。当然,我将定义和lambda的调用分开以使其更易读。


C++20可以吗?或者至少C++17? - max66
是的,c++20可以。 - Damian
请提供一个 [mcve],有些东西被使用但没有声明(m_groups是什么?),有些东西被声明但没有使用(所有常量)。我也不确定 updateComponents 应该做什么(pointersCn*... 的元组,但 updateComponents 只需要两个指针?)。这与问题相关吗?还是问题只是如何构造 pointers - Barry
3个回答

2
添加一些辅助函数:
template <typename Integer, Integer ...I, typename F>
constexpr void constexpr_for_each(std::integer_sequence<Integer, I...>, F &&func)
{
    (func(std::integral_constant<Integer, I>{}) , ...);
}

template <auto N, typename F>
constexpr void constexpr_for(F &&func)
{
    if constexpr (N > 0)
        constexpr_for_each(std::make_integer_sequence<decltype(N), N>{}, std::forward<F>(func));
}

那么你可以这样做:

constexpr_for<sizeof...(Cn)>([&](auto index)
{
    constexpr auto i = index.value;
    std::get<i>(pointers) = pChunk->memory + m_groups.componentOffsets[i];
});

2

如果您接受一个C++20的答案,那么使用一个带有折叠表达式的模板lambda怎么样?[注意:代码未经测试]

std::tuple<Cn*...> pointers;

[&]<std::size_t ... Is>(std::index_sequence<Is...>)
   { ((std::get<Is>(pointers) = pChunk->memory + m_groups.componentOffsets[Is]), ...); }
   (std::make_index_sequence<sizeof...(Cn)>{});
        

兄弟,立即调用的 lambda 表达式不太易读。 - AndyG
1
@AndyG - 不,一开始并不容易读懂。但如果太简单了,那还有什么乐趣呢? - max66
1
可以使用双字符 <: :> <% %> 代替 [ ] { },从而真正增加代码的难读性。 - Eljay
我个人认为,双字符比宏更可怕。 - alter_igel

2
您可以使用lambda和折叠表达式与std::apply配合使用。
std::tuple<Cn*...> pointers;

auto doTheStuff = [&](auto&... ptrs) {
    std::size_t index = 0;
    (ptrs = pChunk->memory + m_groups.componentOffset[index++], ...);
}

std::apply(doTheStuff, pointers);

内置逗号运算符的计算顺序保证从左到右进行所有值的计算和副作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接