如何使用单个函数迭代 C++ 中的不同结构

问题描述 投票:0回答:1

我正在用 C++ 开发实体组件系统 (ECS),并且正在尝试找到一种使用单个函数迭代不同组件结构的有效方法。这是我的组件结构的示例:

struct PositionComponent {
    float x, y, z;
};

struct VelocityComponent {
    float vx, vy, vz;
};

struct RenderComponent {
    // rendering data
};

我想创建一个可以与所有这些组件类型一起使用的通用函数,如下所示:

ForEachComponentDo(function);

其中 function 将对每个组件进行操作,无论其类型如何。

这种方法的关键动机是可维护性和可扩展性。当我将来添加新的组件类型时,我希望尽可能避免修改核心迭代逻辑或为每个新组件添加新的专用功能。

谢谢您的帮助!

c++ design-patterns struct game-development entity-component-system
1个回答
0
投票

虽然您没有 C++ 反射,但在某些情况下,对于“简单”情况,您可以做一些接近您想要的事情。

这个解决方案基于this gist中的想法,主要依赖于结构绑定。

#include <type_traits>
#include <string>
#include <iostream>

template< class T, class ...Args >
inline constexpr bool is_brace_constructible_v = requires { T {std::declval<Args>()...}; };

// We define a universal type.
struct any_type {  template<class T>   constexpr operator T();  };

template<class T, typename FUNCTION>
requires (std::is_class_v<std::decay_t<T>>)
constexpr auto class_fields_call (T&& object, FUNCTION&& f) noexcept
{
    using type = std::decay_t<T>;
    using at   = any_type;
    
    if constexpr(is_brace_constructible_v<type,at,at,at>)
    {
         auto&& [p1,p2,p3] = object;
         return f(p1,p2,p3);
    }
    else if constexpr(is_brace_constructible_v<type,at,at>)
    {
         auto&& [p1,p2] = object;
         return f(p1,p2);
    }
    else if constexpr(is_brace_constructible_v<type,at>)
    {
         auto&& [p1] = object;
         return f(p1);
    }  
}

/** Iterate the fields of a struct/class. Each field is given as argument to a provided functor.  */
template<class T, typename FUNCTION>
auto ForEachComponentDo (T&& object, FUNCTION&& fct) noexcept
{
    class_fields_call (std::forward<T>(object), [fct] (auto&&...args) { ( fct(std::forward<decltype(args)>(args)), ...); });
}

////////////////////////////////////////////////////////////////////////////////

struct A {  int n; double x;  std::string s;  };

int main()
{
    A obj {123, 4.567, "uiop" };

    // We get the fields as an expansion pack
    class_fields_call (obj, [] (auto&&... fields)
    {
        ((std::cout << fields << ' '), ...);
    });
    std::cout << "\n";

    // We get each field as an argument of a function.
    ForEachComponentDo (obj, [] (auto&& field)
    {
        std::cout << field << " ";
    });
    std::cout << "\n";
}

有两个功能可用:

  1. class_fields_call
    将允许获取给定结构实例的字段,作为提供给用户提供的函数的扩展包
  2. ForEachComponentDo
    将通过用户提供的函数迭代每个字段

这个解决方案远非完美,主要缺点是:

  1. 它只能处理最大数量 N 的结构体属性(这里 N=3),但它可以通过代码生成来扩展以获得更大的 N 值;我怀疑递归版本是可以实现的,但我没有付出很大的努力来找到一个。
  2. 要迭代的属性类型必须可以通过大括号初始化来构造,这可能会引发问题(例如 C 数组)。

演示

© www.soinside.com 2019 - 2024. All rights reserved.