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

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

有一个

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

首先我们想实现

template <typename T, T N>
std::string GetEnumName() {
  return __PRETTY_FUNCTION__;
}

这样打印

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

但是这样有问题,因为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)>();
}

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

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

但是很显然这个代码有那么一个问题:函数这样只能判断有限个数值,如果有更多数值呢,写无限个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;
}
};

在这里使用模板递归调用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;
}

你可能感兴趣的:(c++,算法,开发语言)