在 `=` 处拆分宏参数以实现智能枚举
Split macro parameters at `=` for smart enum implementation
有没有办法写一个宏 ENUM
使得 ENUM(Animal, Dog, Cat = 5, Horse = 2)
扩展为
enum class Animal { Dog, Cat = 5, Horse = 2 };
template<typename EnumT>
constexpr std::array<std::pair<std::string_view, Animal>, 3> get_enum_map() {
return {{ {"Dog", Animal::Dog} , {"Cat", Animal::Cat} , {"Horse", Animal::Horse} }};
}
注意,难点在于宏参数NAME = VAL
需要在=
处拆分得到NAME
,没有VAL
,这样我们就可以定义get_enum_map()
干净利落。 可以这样做吗?
作为参考,下面的代码实现了 ENUM(Animal, Dog, Cat, Horse)
(类似于 wise_enum),但无法为枚举指定值。我见过诸如 ENUM(Animal, Dog, (Cat, 5), (Horse , 2))
之类的示例,其中各个参数被打包为 (NAME, VAL)
。但我很好奇是否有办法通过 NAME = VAL
.
#include <string_view>
#include <array>
#define ENUM_EXPAND(x) x
#define ENUM_COMMA() ,
#define ENUM_STR_CONCAT(a, ...) ENUM_STR_CONCAT_(a, __VA_ARGS__)
#define ENUM_STR_CONCAT_(a, ...) a##__VA_ARGS__
#define ENUM_ARG_COUNT_(_1, _2, _3, _4, _5, X,...) X
#define ENUM_ARG_COUNT(...) ENUM_EXPAND(ENUM_ARG_COUNT_(__VA_ARGS__, 5, 4, 3, 2, 1))
#define ENUM_LOOP_1(f, a, b, c) f(a, c)
#define ENUM_LOOP_2(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_1(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_3(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_2(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_4(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_3(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_5(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_4(f, a, b, __VA_ARGS__))
#define ENUM_PAIR(name, x) { #x, name::x}
#define ENUM_IMPL(name, loop_func_name, ...) \
enum class name { __VA_ARGS__ }; template<typename EnumT> \
constexpr std::array<std::pair<std::string_view, name>, ENUM_ARG_COUNT(__VA_ARGS__)> get_enum_map() { \
return {{ ENUM_EXPAND(loop_func_name(ENUM_PAIR, name, ENUM_COMMA, __VA_ARGS__)) }}; }
#define ENUM(name, ...) ENUM_IMPL(name, ENUM_STR_CONCAT(ENUM_LOOP_, ENUM_ARG_COUNT(__VA_ARGS__)), __VA_ARGS__)
ENUM(Animal, Dog, Cat, Horse)
int main() {
constexpr auto arr = get_enum_map<Animal>();
constexpr auto el0 = std::get<0>(arr[1]);
constexpr auto el1 = std::get<1>(arr[1]);
static_assert(el0 == "Cat");
static_assert(el1 == Animal::Cat);
return 0;
}
正如评论中指出的那样,无法在某个字符处拆分宏输入,例如 =
。
然而,better-enums 库通过 _eat_assign
帮助程序 class 吸收(或“吃掉”)分配来使用巧妙的技巧。例如
(EnumType)((better_enums::_eat_assign<EnumType>)EnumType::Name = EnumValue)
减少到 EnumType::Name
,有效地删除了分配。参见 _eat_assign definition。
有没有办法写一个宏 ENUM
使得 ENUM(Animal, Dog, Cat = 5, Horse = 2)
扩展为
enum class Animal { Dog, Cat = 5, Horse = 2 };
template<typename EnumT>
constexpr std::array<std::pair<std::string_view, Animal>, 3> get_enum_map() {
return {{ {"Dog", Animal::Dog} , {"Cat", Animal::Cat} , {"Horse", Animal::Horse} }};
}
注意,难点在于宏参数NAME = VAL
需要在=
处拆分得到NAME
,没有VAL
,这样我们就可以定义get_enum_map()
干净利落。 可以这样做吗?
作为参考,下面的代码实现了 ENUM(Animal, Dog, Cat, Horse)
(类似于 wise_enum),但无法为枚举指定值。我见过诸如 ENUM(Animal, Dog, (Cat, 5), (Horse , 2))
之类的示例,其中各个参数被打包为 (NAME, VAL)
。但我很好奇是否有办法通过 NAME = VAL
.
#include <string_view>
#include <array>
#define ENUM_EXPAND(x) x
#define ENUM_COMMA() ,
#define ENUM_STR_CONCAT(a, ...) ENUM_STR_CONCAT_(a, __VA_ARGS__)
#define ENUM_STR_CONCAT_(a, ...) a##__VA_ARGS__
#define ENUM_ARG_COUNT_(_1, _2, _3, _4, _5, X,...) X
#define ENUM_ARG_COUNT(...) ENUM_EXPAND(ENUM_ARG_COUNT_(__VA_ARGS__, 5, 4, 3, 2, 1))
#define ENUM_LOOP_1(f, a, b, c) f(a, c)
#define ENUM_LOOP_2(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_1(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_3(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_2(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_4(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_3(f, a, b, __VA_ARGS__))
#define ENUM_LOOP_5(f, a, b, c, ...) f(a, c) b() ENUM_EXPAND(ENUM_LOOP_4(f, a, b, __VA_ARGS__))
#define ENUM_PAIR(name, x) { #x, name::x}
#define ENUM_IMPL(name, loop_func_name, ...) \
enum class name { __VA_ARGS__ }; template<typename EnumT> \
constexpr std::array<std::pair<std::string_view, name>, ENUM_ARG_COUNT(__VA_ARGS__)> get_enum_map() { \
return {{ ENUM_EXPAND(loop_func_name(ENUM_PAIR, name, ENUM_COMMA, __VA_ARGS__)) }}; }
#define ENUM(name, ...) ENUM_IMPL(name, ENUM_STR_CONCAT(ENUM_LOOP_, ENUM_ARG_COUNT(__VA_ARGS__)), __VA_ARGS__)
ENUM(Animal, Dog, Cat, Horse)
int main() {
constexpr auto arr = get_enum_map<Animal>();
constexpr auto el0 = std::get<0>(arr[1]);
constexpr auto el1 = std::get<1>(arr[1]);
static_assert(el0 == "Cat");
static_assert(el1 == Animal::Cat);
return 0;
}
正如评论中指出的那样,无法在某个字符处拆分宏输入,例如 =
。
然而,better-enums 库通过 _eat_assign
帮助程序 class 吸收(或“吃掉”)分配来使用巧妙的技巧。例如
(EnumType)((better_enums::_eat_assign<EnumType>)EnumType::Name = EnumValue)
减少到 EnumType::Name
,有效地删除了分配。参见 _eat_assign definition。