• C++实现enum反射,类似magic_enum,支持enum classes


    C++实现enum反射,类似magic_enum,支持enum classes

    有一个

    enum EnumTest { a = 1, b, c };
    
    • 1

    首先我们想实现

    template <typename T, T N>
    std::string GetEnumName() {
      return __PRETTY_FUNCTION__;
    }
    
    • 1
    • 2
    • 3
    • 4

    这样打印

    GetEnumName(2)>()

    出来的就是

    std::string GetEnumName() [with T = EnumTest; T N = b; std::string = std::__cxx11::basic_string]

    对于这个字符串,我们只需要知道; T N = 后面跟的内容是啥就实现了我们的目标

    但是我们不想像这样把参数写在模板了,而是想用一个函数直接传参

    也就是想实现

    template <typename T, T N>
    std::string GetEnumNameImp() {
      std::string tmp = __PRETTY_FUNCTION__;
      auto first = tmp.find("T N = ");
      first += 6;
      auto end = tmp.find(";", first);
      return std::string(tmp, first, end - first);
    }
    
    template <typename T>
    std::string GetEnumName(T n) {
      return GetEnumNameImp<T,n>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    但是这样有问题,因为n是变量,无法传入模板。

    那可以考虑这样写:

    template <typename T>
    std::string GetEnumName(T n) {
      if (n == 1) return GetEnumNameImp<T, T(1)>();
      if (n == 2) return GetEnumNameImp<T, T(2)>();
      if (n == 3) return GetEnumNameImp<T, T(3)>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样子可以保证传入模板的是常量并且可以动态判断了

    到这里我们其实已经可以实现反射了。

    但是很显然这个代码有那么一个问题:函数这样只能判断有限个数值,如果有更多数值呢,写无限个if吗

    我们需要简化一下这个代码,让它能够自动执行完这么多个if,那么if的开始和结束呢?

    没有特别好的方法做判断,我们只能先预定定义好开始和结束,比如Magic Enum 默认定义的就是-128~+128

    开始实现代码

    首先需要介绍一种模板for循环的小trick

    template <int begin, int end, typename F>
    typename std::enable_if<begin == end>::type TemplateForLoop(const F &fun) {
    fun.template call<begin>();
    }
    template <int begin, int end, typename F>
    typename std::enable_if<begin != end>::type TemplateForLoop(const F &fun) {
    fun.template call<begin>();
    TemplateForLoop<begin + 1, end>(fun);
    }
    
    struct TestClass {
    template <int N>
    void call() const {
     std::cout << "N=" << N << std::endl;
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里使用模板递归调用fun函数,并把begin传入到fun中

    使用 TestClass<10,20>(TestClass()); 就会循环打印[10,20]

    注意这里的begin和end是左闭右闭的,如果需要左闭右开,可以在begin==end时不执行fun

    有了模板for循环,接来下就比较简单了

    // enum imp
    namespace detail {
    
    // Enum value must be greater or equals than G_CONFIG_ENUM_RANGE_MIN. By default
    // G_CONFIG_ENUM_RANGE_MIN = -128. If need another min range for all enum types
    // by default, redefine the macro G_CONFIG_ENUM_RANGE_MIN.
    #if !defined(G_CONFIG_ENUM_RANGE_MIN)
    #define G_CONFIG_ENUM_RANGE_MIN -128
    #endif
    
    // Enum value must be less or equals than G_CONFIG_ENUM_RANGE_MAX. By default
    // G_CONFIG_ENUM_RANGE_MAX = 128. If need another max range for all enum types
    // by default, redefine the macro G_CONFIG_ENUM_RANGE_MAX.
    #if !defined(G_CONFIG_ENUM_RANGE_MAX)
    #define G_CONFIG_ENUM_RANGE_MAX 128
    #endif
    
    static_assert(G_CONFIG_ENUM_RANGE_MAX < std::numeric_limits<int>::max(),
                  "G_CONFIG_ENUM_RANGE_MAX must be less than INT_MAX.");
    
    static_assert(G_CONFIG_ENUM_RANGE_MIN > std::numeric_limits<int>::min(),
                  "G_CONFIG_ENUM_RANGE_MIN must be greater than INT_MIN.");
    
    template <typename T, T N>
    inline std::string GetEnumNameImp() {
    #if defined(__GNUC__) || defined(__clang__)
      std::string tmp = __PRETTY_FUNCTION__;
      auto first = tmp.find("T N = ");
      first += 6;
      auto end = tmp.find(";", first);
      return std::string(tmp, first, end - first);
    #elif defined(_MSC_VER)
      // TODO: add support for msvc
    #else
    #endif
    }
    
    template <int begin, int end, typename F>
    typename std::enable_if<begin == end>::type TemplateForLoop(const F &fun) {
      fun.template call<begin>();
    }
    template <int begin, int end, typename F>
    typename std::enable_if<begin != end>::type TemplateForLoop(const F &fun) {
      fun.template call<begin>();
      TemplateForLoop<begin + 1, end>(fun);
    }
    
    
    template <typename T>
    struct GetEnumClass {
      std::string &str_;
      int n_;
      GetEnumClass(int n, std::string &str) : n_(n), str_(str) {}
    
      template <int N>
      void call() const {
        if (n_ == N) {
          str_ = detail::GetEnumNameImp<T, T(N)>();
        }
      }
    };
    
    } // detail for enum imp
    
    template <typename T, int min = G_CONFIG_ENUM_RANGE_MIN,
              int max = G_CONFIG_ENUM_RANGE_MAX>
    inline std::string GetEnumName(T n) {
      std::string str;
      gxt::detail::TemplateForLoop<min, max>(
          gxt::detail::GetEnumClass<T>(static_cast<int>(n), str));
      if (str.empty()) {
        throw std::runtime_error("\nenum out of range\n");
      }
      return str;
    }
    
    template <typename T, int min = G_CONFIG_ENUM_RANGE_MIN,
              int max = G_CONFIG_ENUM_RANGE_MAX>
    inline int GetNameEnum(std::string name) {
      std::string str;
      for (int i = G_CONFIG_ENUM_RANGE_MIN; i <= G_CONFIG_ENUM_RANGE_MAX; i++) {
        gxt::detail::TemplateForLoop<G_CONFIG_ENUM_RANGE_MIN,
                                     G_CONFIG_ENUM_RANGE_MAX>(
            gxt::detail::GetEnumClass<T>(static_cast<int>(i), str));
        if (!str.empty()) {  // solve bug that use class enum
          auto find = str.find("::");
          if (find != std::string::npos) {
            find += 2;
            str = std::string(str, find);
          }
        }
        if (!str.empty() && str == name) {
          return i;
        }
      }
      throw std::runtime_error("\nenum out of range\n");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
  • 相关阅读:
    2023数学建模国赛选题建议及BC题思路
    数据库-MySQL
    hot100 -- 图论
    uniapp vue 页面传参问题encodeURIComponent
    后端——面试题-注册、登录、数据库egg-mysql、svg验证码、服务层Service
    代理服务器没有响应,谷歌浏览器无法上网【搬代码】
    基于ESP8266+BY8301语音模块的与山地车捉迷藏的小项目
    Syncro 深化与 Splashtop 的协作以简化远程访问
    面试官:我们深入聊聊Java虚拟机吧
    Qt QFrame详解
  • 原文地址:https://blog.csdn.net/my_id_kt/article/details/133707241