我正在用 C++ 开发实体组件系统 (ECS),并且正在尝试找到一种使用单个函数迭代不同组件结构的有效方法。这是我的组件结构的示例:
struct PositionComponent {
float x, y, z;
};
struct VelocityComponent {
float vx, vy, vz;
};
struct RenderComponent {
// rendering data
};
我想创建一个可以与所有这些组件类型一起使用的通用函数,如下所示:
ForEachComponentDo(function);
其中 function 将对每个组件进行操作,无论其类型如何。
这种方法的关键动机是可维护性和可扩展性。当我将来添加新的组件类型时,我希望尽可能避免修改核心迭代逻辑或为每个新组件添加新的专用功能。
谢谢您的帮助!
虽然您没有 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";
}
有两个功能可用:
class_fields_call
将允许获取给定结构实例的字段,作为提供给用户提供的函数的扩展包ForEachComponentDo
将通过用户提供的函数迭代每个字段这个解决方案远非完美,主要缺点是: