简体   繁体   English

c++ 元编程:为每个枚举类型成员创建 typedef 作为类型

[英]c++ metaprogramming: creating typedef for each enum type member as type

I wonder if it is possible to generate types set from enum class for the metaprogramming purposes.我想知道是否可以从枚举 class 生成类型集以用于元编程目的。 I'm originally a C# programmer and used to using a lot of attributes for reflection and metaprogramming.我最初是一名 C# 程序员,习惯于使用很多属性进行反射和元编程。 For example, it is a general pattern for me to write a snippet like that with 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)];
}

Of course, it is a very basic snippet without asserts and some other stuff but I believe you got the idea.当然,这是一个非常基本的片段,没有断言和其他一些东西,但我相信你明白了。 I want to make the same behavior in the c++ snippet.我想在 c++ 片段中做出同样的行为。 What I want to do exactly is makes a type called Components that will contain some utility functions like bool Components::isComponent<T>() or size_t Components::getComponentUID<T>() or some related stuff.我真正想做的是创建一个名为Components的类型,它将包含一些实用函数,如bool Components::isComponent<T>()size_t Components::getComponentUID<T>()或一些相关的东西。 The best way I've seen so far is to write it down by myself, making a metaclass like到目前为止我见过的最好的方法是自己写下来,制作一个像

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

typedef ComponentsData<C1, C2, C3> Components;

So, now I can ask Components<C1>::getComponentUID() and it returns me uid of that component (depends on its position as template parameter or constexpr value of that component, it doesn't matter).所以,现在我可以询问Components<C1>::getComponentUID()并返回该组件的 uid(取决于它的 position 作为模板参数或该组件的 constexpr 值,没关系)。 But it is a very inconvenient way to do that and I wonder if I can put a macro inside the component class or using attributes and code generation step or something.但这是一种非常不方便的方法,我想知道是否可以将宏放入组件 class 或使用属性和代码生成步骤之类的。 In other words, my goal is to mark somehow the class that it should be in that components set and use it later.换句话说,我的目标是以某种方式标记 class 它应该在该组件集中并在以后使用它。 What c++ can offer for that purpose? c++ 可以为此提供什么?

It will be okay if I could make something like I did C# way - make an enum class, list all the components there, and write a constexpr value inside a component class (or somewhere near the enum class, both ways is good for me). It will be okay if I could make something like I did C# way - make an enum class, list all the components there, and write a constexpr value inside a component class (or somewhere near the enum class, both ways is good for me) . I mean something like that:我的意思是这样的:

    /* 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; };

or something like that或类似的东西

    /* 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 { };

or maybe something with macro magic:或者可能是带有宏观魔法的东西:

    /* ComponentsEnum.h */
    enum class ComponentsEnum { Comp1, Comp2, Comp3 };
    #define InitMeta(ComponentsEnumMember) /* Some Magic */

    /* another file */
    #include "ComponentsEnum.h"
    struct Comp1 { InitMeta(ComponentsEnum::Comp1) };

Thanks in advance!提前致谢!

Following on my comment.关注我的评论。

You could do something like this in C++17:您可以在 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 requires moving the definition and initialization to a .cpp for each component::id . C++17 之前的版本需要将每个component::id的定义和初始化移动到.cpp中。

But I strongly recommend not to use this.但我强烈建议不要使用它。 Rethink your design, converting types to IDs is a code smell for me.重新考虑你的设计,将类型转换为 ID 对我来说是一种代码味道。 C++ is not really designed to do such things, it can haunt you later. C++ 并不是真正设计用来做这些事情的,它可能会在以后困扰你。

The code above relies on dynamic initialization of all static variable at the start of the program.上面的代码依赖于程序开始时所有 static 变量的动态初始化。 The order is unspecified, each compilation might result in assignment of different IDs.顺序未指定,每次编译可能会导致分配不同的 ID。

Definitely do not put this into any shared libraries before being 100% sure you know how the compilation, linking, and loading processes work for your toolchain because these are outside the scope of C++ Standard.绝对不要在 100% 确定您知道编译、链接和加载过程如何为您的工具链工作之前将其放入任何共享库中,因为这些不在 C++ 标准的 scope 范围内。

Thanks to the @JerryJeremiah link and @Quimby advice, I found the solution.感谢@JerryJeremiah 链接和@Quimby 的建议,我找到了解决方案。

So, I was misled by my C# habits and the idea was quite simple but tricky.所以,我被我的 C# 习惯误导了,这个想法很简单但很棘手。 According to the difference between C# generics and C++ templates, generics are runtime instanced types, but templates are compile-time types.根据 C# generics 和 C++ 模板之间的区别,Z56B97998B338B977FF5A 模板是运行时类型,但是模板是运行时类型92846。 So, I do not need to create a map or process the whole codebase, all I need will be generated with templates in compile time.因此,我不需要创建 map 或处理整个代码库,我所需要的只是在编译时使用模板生成。

The solution itself:解决方案本身:

  1. I want an enum to generate continuous uid numbers for my components.我想要一个枚举来为我的组件生成连续的 uid 号。 So, define it:所以,定义它:
enum class ComponentEnum
{
    C1,
    C2,
    C3
};
  1. I want a simple interface for my Components to ask for meta information.我希望我的组件有一个简单的界面来询问元信息。 Define it too:也定义它:
struct Components
{
    template<typename T>
    static bool isComponent() { /* Some stuff here */ }
    
    template<typename T>
    static int getComponentUID() { /* Some stuff here */ }
};

Now I can ask uid with one simple generalized call Components::getComponentUID<MyComponent>() .现在我可以通过一个简单的通用调用Components::getComponentUID<MyComponent>()来询问 uid。 Nice.好的。

  1. The real magic.真正的魔法。 I've created template metaclass and macro to create a typedef and some additional methods:我创建了模板元类和宏来创建 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; }

So I can fill methods from my interface with simple forwarding to that metaclass:所以我可以通过简单的转发到那个元类来填充我的接口中的方法:

struct Components
{
    template<typename T>
    static bool isComponent() { return T::Meta::isComponent; }
    
    template<typename T>
    static int getComponentUID() { return T::Meta::uid; }
};
  1. All things left is include header with metaclass and macro and call the macro:剩下的就是包含带有元类和宏的 header 并调用宏:
struct C1
{
    ComponentMetaMacro(C1, C1)
};

struct C2
{
    ComponentMetaMacro(C2, C2)
};

Run a few tests:运行几个测试:

    std::cout << C1::toString() << ": " << Components::getComponentUID<C1>() << std::endl;
    std::cout << C2::toString() << ": " << Components::getComponentUID<C2>() << std::endl;

C1: 0 C2: 1 C1: 0 C2: 1

Yay!耶!


This solution has three main problems:该解决方案存在三个主要问题:

  1. isComponent() becomes the static assert instead of the flag. isComponent() 变为 static 断言而不是标志。 I mean, the code won't compile if T-type is not a component.我的意思是,如果 T-type 不是组件,则代码将无法编译。 It is quite ok but smells.这很好,但有气味。
  2. It is a single linked meta.它是一个单链接元。 I can't get a component type from the index, only an index from the type.我无法从索引中获取组件类型,只能从类型中获取索引。 But for serialization purposes, it could be useful to have a backlink.但出于序列化目的,有一个反向链接可能很有用。
  3. I should include the enum class to every component header.我应该将枚举 class 包含到每个组件 header 中。 It means there will be a huge compile-time affect when I will add a new enum member.这意味着当我添加一个新的枚举成员时会产生巨大的编译时影响。 I suppose there is a way to avoid it but can't see one.我想有一种方法可以避免它,但看不到。 The only enum class purpose is to have the smallest index as possible for every component that will be static between compilations.唯一的枚举 class 的目的是为每个在编译之间为 static 的组件提供尽可能小的索引。 Maybe I have to think about some data generation or another approaches, but for the small project it is ok.也许我必须考虑一些数据生成或其他方法,但对于小项目来说还可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM