我想知道是否有可能从enum类生成用于元编程目的的类型集。我最初是一名C#程序员,习惯于使用许多属性进行反射和元编程。例如,对我来说,用C#编写这样的代码片段是一种通用模式:
public enum ComponentEnum { Component1, Component2, Component3 }
[Component(ComponentEnum.Component1)]
public class Component1
{
/* Some code */
}
public static class ComponentsMeta
{
private static Dictionary<Type, ComponentEnum> map;
static ComponentMeta() { /*process the whole codebase via reflection, search Component marked classes an fill the map */}
public static bool IsComponent<T>() => map.ContainsKey(typeof(T));
public static int GetComponentUID<T>() => (int)map[typeof(T)];
}
当然,这是一个非常基本的片段,没有断言和其他东西,但我相信你已经明白了。我想在c++代码段中做出同样的行为。我想做的是创建一个名为Components
的类型,它将包含一些实用函数,如bool Components::isComponent<T>()
或size_t Components::getComponentUID<T>()
或一些相关的东西。到目前为止,我看到的最好的方法是自己写下来,制作一个类似的元类
template <typename Ts..>
class ComponentsData
{
/* functions impl here */
}
typedef ComponentsData<C1, C2, C3> Components;
所以,现在我可以询问Components<C1>::getComponentUID()
,它会返回该组件的uid(取决于它作为该组件的模板参数或constexpr值的位置,这无关紧要(。但这是一种非常不方便的方法,我想知道是否可以在组件类中放入宏,或者使用属性和代码生成步骤或其他方法。换句话说,我的目标是以某种方式将类标记为它应该在该组件集中,并在以后使用它。c++可以为此提供什么?
如果我能像C#那样制作一些东西,那就没问题了——制作一个枚举类,列出那里的所有组件,并在组件类内写一个constexpr值(或者在枚举类附近的某个地方,这两种方式对我都很好(。我的意思是:
/* ComponentsEnum.h */
enum class ComponentsEnum { Comp1, Comp2, Comp3 };
// Here is some magic to generate Components<C1, C2, C3> metaclass.
/* another file */
#include "ComponentsEnum.h"
struct C1 { const ComponentsEnum MyValue = ComponentsEnum::Comp1; };
或者类似的东西
/* ComponentsEnum.h */
enum class ComponentsEnum { Comp1, Comp2, Comp3 };
// Here is all the magic
// All enum members concats into `Components<Comp1, Comp2, Comp3, ...>`
ConcatAll<ComponentsEnum>();
/* another file */
#include "ComponentsEnum.h"
struct Comp1 { };
或者可能是一些具有宏魔力的东西:
/* ComponentsEnum.h */
enum class ComponentsEnum { Comp1, Comp2, Comp3 };
#define InitMeta(ComponentsEnumMember) /* Some Magic */
/* another file */
#include "ComponentsEnum.h"
struct Comp1 { InitMeta(ComponentsEnum::Comp1) };
提前感谢!
下面是我的评论。
你可以在C++17:中做这样的事情
// In register.hpp
int register_me();
// In register.cpp
int register_me(){
static int id = 0;
return id++;
}
// In wherever.hpp
// #include "register.hpp"
struct component{
inline static int id = register_me();
};
Pre-C++17要求将定义和初始化移动到每个component::id
的.cpp
。
但我强烈建议不要使用这个。重新思考你的设计,将类型转换为ID对我来说是一种代码气味。C++并不是真正为做这些事情而设计的,它可能会在以后困扰你。
上面的代码依赖于程序开始时所有静态变量的动态初始化。顺序未指定,每次编译都可能导致分配不同的ID。
在100%确定编译、链接和加载过程如何为工具链工作之前,绝对不要将其放入任何共享库中,因为这些都超出了C++标准的范围。
感谢@JerryJeremiah链接和@Quimby的建议,我找到了解决方案。
所以,我被我的C#习惯误导了,这个想法很简单,但很棘手。根据C#泛型和C++模板之间的区别,泛型是运行时实例化的类型,但模板是编译时类型。因此,我不需要创建映射或处理整个代码库,我所需要的都将在编译时用模板生成。
解决方案本身:
- 我想要一个枚举来为我的组件生成连续的uid号。因此,定义它:
enum class ComponentEnum
{
C1,
C2,
C3
};
- 我想要一个简单的组件接口来请求元信息。也定义它:
struct Components
{
template<typename T>
static bool isComponent() { /* Some stuff here */ }
template<typename T>
static int getComponentUID() { /* Some stuff here */ }
};
现在我可以通过一个简单的广义调用Components::getComponentUID<MyComponent>()
来询问uid。美好的
- 真正的魔法。我创建了模板元类和宏来创建typedef和一些其他方法:
template <typename T, ComponentEnum enumMember>
struct ComponentMeta
{
static constexpr bool isComponent = true;
static constexpr int uid = static_cast<int>(enumMember);
};
#define ComponentMetaMacro(type_name, enum_name) typedef ComponentMeta<type_name, ComponentEnum::enum_name> Meta;
static const char* toString() { return #type_name; }
因此,我可以通过简单的转发将接口中的方法填充到元类:
struct Components
{
template<typename T>
static bool isComponent() { return T::Meta::isComponent; }
template<typename T>
static int getComponentUID() { return T::Meta::uid; }
};
- 剩下的所有东西都包括带有元类和宏的头,并调用宏:
struct C1
{
ComponentMetaMacro(C1, C1)
};
struct C2
{
ComponentMetaMacro(C2, C2)
};
运行一些测试:
std::cout << C1::toString() << ": " << Components::getComponentUID<C1>() << std::endl;
std::cout << C2::toString() << ": " << Components::getComponentUID<C2>() << std::endl;
C1: 0
C2: 1
耶!
此解决方案有三个主要问题:
- isComponent((变成静态断言而不是标志。我的意思是,如果t型不是一个组件,代码就不会编译。还可以,但闻起来很香
- 它是一个单链接的元。我无法从索引中获取组件类型,只能从类型中获取索引。但是为了序列化的目的,有一个反向链接可能是有用的
- 我应该在每个组件头中都包含enum类。这意味着当我添加一个新的枚举成员时,将对编译时间产生巨大影响。我想有办法避开它,但看不到。枚举类的唯一目的是为编译之间的每个静态组件提供尽可能小的索引。也许我必须考虑一些数据生成或其他方法,但对于小项目来说,这是可以的