【问题标题】:c++ metaprogramming: creating typedef for each enum type member as typec ++元编程:为每个枚举类型成员创建typedef作为类型
【发布时间】:2020-12-03 22:31:18
【问题描述】:

我想知道是否可以从枚举类生成类型集以用于元编程目的。 我最初是一名 C# 程序员,习惯于使用很多属性进行反射和元编程。例如,对我来说,用 C# 编写类似的 sn-p 是一种通用模式:

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

当然,这是一个非常基本的 sn-p,没有断言和其他一些东西,但我相信你明白了。 我想在 c++ sn-p 中做出同样的行为。我真正想做的是创建一个名为Components 的类型,它将包含一些实用函数,如bool Components::isComponent&lt;T&gt;() 或size_t Components::getComponentUID&lt;T&gt;() 或一些相关的东西。到目前为止我见过的最好的方法是自己写下来,制作一个类似的元类

template <typename Ts..>
class ComponentsData
{
/* functions impl here */
}

typedef ComponentsData<C1, C2, C3> Components;

所以,现在我可以询问Components&lt;C1&gt;::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++23。但是你试图做的不是一个好主意 - 为类型分配一些项目范围的 ID 违背了单独的编译机制,你将很难保持一切同步。有了足够多的全局变量和它们的静态初始化,它可能就可以完成,但不是在编译时。
  • @Quimby 好的,那么不使用枚举,而是使用另一种机制呢?代码生成,属性标记,还是什么?只是外部变量初始化?
  • 我做了一个回答,因为它不适合评论。它做了 ID 的事情,但实际上,如果你想在任何严肃的项目中使用它,请先尝试找到其他东西,或者至少做足够的研究以真正理解代码中每个关键字的作用。
  • 这是一个想法:eliasdaler.github.io/meta-stuff

标签: c++ metaprogramming


【解决方案1】:

关注我的评论。

你可以在 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();
};


C++17 之前的版本需要将每个 component::id 的定义和初始化移动到 .cpp。

但我强烈建议不要使用它。重新考虑你的设计,将类型转换为 ID 对我来说是一种代码味道。 C++ 并不是真正设计用来做这些事情的,它可能会在以后困扰你。

上面的代码依赖于程序开始时所有静态变量的动态初始化。顺序未指定,每次编译可能会导致分配不同的 ID。

在 100% 确定您知道编译、链接和加载过程如何为您的工具链工作之前,绝对不要将其放入任何共享库中,因为这些超出了 C++ 标准的范围。

【讨论】:

    【解决方案2】:

    感谢@JerryJeremiah 链接和@Quimby 的建议,我找到了解决方案。

    所以,我被我的 C# 习惯误导了,这个想法很简单但很棘手。 根据 C# 泛型和 C++ 模板的区别,泛型是运行时实例类型,而模板是编译时类型。所以,我不需要创建地图或处理整个代码库,我需要的只是在编译时使用模板生成。

    解决方案本身:

    1. 我想要一个枚举来为我的组件生成连续的 uid 号。所以,定义它:
    enum class ComponentEnum
    {
        C1,
        C2,
        C3
    };
    
    1. 我希望我的组件有一个简单的界面来询问元信息。也定义它:
    struct Components
    {
        template<typename T>
        static bool isComponent() { /* Some stuff here */ }
        
        template<typename T>
        static int getComponentUID() { /* Some stuff here */ }
    };
    

    现在我可以通过一个简单的通用调用 Components::getComponentUID&lt;MyComponent&gt;() 来询问 uid。不错。

    1. 真正的魔法。我创建了模板元类和宏来创建 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; }
    };
    
    1. 剩下的就是包含带有元类和宏的标头并调用宏:
    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

    耶!


    这个解决方案主要存在三个问题:

    1. isComponent() 成为静态断言而不是标志。我的意思是,如果 T-type 不是组件,则代码将无法编译。还可以,但有异味。
    2. 这是一个单链接元。我无法从索引中获取组件类型,只能从类型中获取索引。但出于序列化目的,有一个反向链接可能会很有用。
    3. 我应该在每个组件头中包含枚举类。这意味着当我添加一个新的枚举成员时会产生巨大的编译时影响。我想有一种方法可以避免它,但看不到。唯一的枚举类目的是为每个在编译之间将是静态的组件具有尽可能小的索引。也许我必须考虑一些数据生成或其他方法,但对于小项目来说还可以。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多