有一个
enum EnumTest { a = 1, b, c };
首先我们想实现
template <typename T, T N>
std::string GetEnumName() {
return __PRETTY_FUNCTION__;
}
这样打印
GetEnumName
出来的就是
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;
}