前言
C++11引入了强类型的枚举类 enum class
用来代替旧风格枚举enum
,新引入的 enum class
具有诸多优点:防止命名空间污染,不能隐式的转换为整型,防止不同类型的枚举相互赋值,支持前置声明。当然它也不是只有优点,因为类型不能隐式转换成int,所以在使用或者输出时需要使用 static_cast
进行转换,不过即便使用 static_cast
可以转换后输出,也不便于我们辨识枚举的值,如果想输出枚举定义时的名字就需要使用一些魔法了。
magic_enum
因为C++本身不支持反射,或者说反射能力极弱,所以想反射我们必须自己实现一些东西,比如 UE
引擎就为C++写了一套自己的反射标签,而我们想获得枚举定义时的名字就需要自己记录了,因为编译后的枚举一般都转化成了整数,一个简单粗暴的想法是在定义时为每个枚举值同时指定一个同名字符串,构成map存储下来,不过我们不想每次都自己来做这件事,要是有人能帮忙就好了,这不它来了, magic_enum 就可以帮你实现这个愿望。
简单介绍
magic_enum 是一个单头文件的开源库,使用方便,可以轻松帮你实现打印枚举值定义时名字的需求,另外除了可以实现这个功能,还可以根据字符串生成枚举值,根据整数生成枚举值,获取枚举值数组,获取枚举值和名字对应的数字组等等,简直是一个封装了枚举操作的宝库。
具体使用
直接引用头文件 magic_enum.hpp
,然后调用函数 magic_enum::enum_name(enum_xxx)
即可:
#include <iostream>
#include "magic_enum.hpp"
enum class WeekDay
{
WD_SUNDAY = 0,
WD_MONDAY,
WD_TUESDAY,
WD_WEDNESDAY,
WD_THURSDAY,
WD_FRIDAY,
WD_SATURDAY,
};
int main()
{
WeekDay day = WeekDay::WD_MONDAY;
std::cout << "enum value: " << static_cast<std::underlying_type<WeekDay>::type>(day) << "\n";
std::cout << "enum name: " << magic_enum::enum_name(day) << "\n";
return 0;
}
编译运行如下:
$ g++ testenum.cpp -std=c++17 && ./a.out
enum value: 1
enum name: WD_MONDAY
原理简述
很神奇对不对,其实枚举值转换成字符串这一步,是是利用了函数模板和 __PRETTY_FUNCTION__
组合使用获得到的,也就是对 __PRETTY_FUNCTION__
进行截取得到的字符串。
__PRETTY_FUNCTION__
在预编译阶段会替换成带有参数的函数名,比如 constexpr auto magic_enum::detail::n() [with E = WeekDay; E e = WD_MONDAY]
从中截取出 WD_MONDAY
就可以了。
局限性
为了实现从字符串到枚举值的转换,这个库的内部定义了一个整数范围,默认从-128到128,用于遍历查找字符串对应的枚举值是多少,并且在代码中加了 static_assert
来判断范围,如果超过了这个范围就会报编译错误,这个范围可以通过修改源码中的 MAGIC_ENUM_RANGE_MIN
和 MAGIC_ENUM_RANGE_MAX
重新编译来修改,在这个范围之外还有个最大值 std::numeric_limits<std::uint16_t>::max
的限制。
这个最大限制也是可以改的,不过我尝试把 MAGIC_ENUM_RANGE_MAX
改到上限值 32767 之后编译时间明显变成,编译过程变得异常的慢,单个文件编译30秒,所以不建议把这个值调太大。
对比一下不使用 magic_enum
和使用它之后生成的汇编代码,从100多行扩充到1000多行,利用 type_traits
生成了大量的函数:
...
.type _ZN10magic_enum6detail7names_vI7WeekDayEE, @gnu_unique_object
.size _ZN10magic_enum6detail7names_vI7WeekDayEE, 112
_ZN10magic_enum6detail7names_vI7WeekDayEE:
.quad 9
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_0EEE
.quad 9
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_1EEE
.quad 10
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_2EEE
.quad 12
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_3EEE
.quad 11
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_4EEE
.quad 9
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_5EEE
.quad 11
.quad _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_6EEE
.weak _ZN10magic_enum6detail11enum_name_vI7WeekDayLS2_0EEE
...
简化与改进
其实我最想要的还是通过枚举值转化转化成字符串名称的功能,可以将这个开源库简化一下,仅保留这个功能,这样也不会有范围限制了,感觉这个库为了实现从字符串到枚举值转换背负了太多,去掉它会很清爽。
总结
magic_enum
是一个开源的、单头文件的、枚举操作工具箱magic_enum
可以实现枚举值到字符串、字符串到枚举值、获取所有枚举名等多种操作magic_enum
本身对枚举值有范围限制,默认是 [-128, 128], 可通过MAGIC_ENUM_RANGE_MIN
和MAGIC_ENUM_RANGE_MAX
修改- 不建议将
magic_enum
默认枚举范围改的太大,这会明显拖慢编译时间
世界上有那么多美好,不要跟自己过不去,总是揪着那些角落里的肮脏不放。我们无法选择抓到什么牌,但可以决定怎么把已经抓到手的牌打出去,摆烂是一天,奋斗也是一天,究竟要怎么做,取决于你自己~