跳到主要内容

探索 magic_enum:C++ 枚举的魔法工具

在 C++ 开发中,枚举(Enum)是一种非常有用的数据类型,它可以帮助我们定义一组命名的常量。然而,C++ 标准库对枚举的支持相对有限,尤其是在枚举值与字符串之间的转换上。这时,magic_enum 库就派上了用场。magic_enum 是一个轻量级的 C++ 库,它提供了许多强大的功能,使得枚举类型的操作更加方便和灵活。

1. 什么是 magic_enum?

magic_enum 是一个基于 C++17 的库,它通过模板元编程和反射技术,提供了对枚举类型的增强支持。它的主要功能包括:

  • 枚举值与字符串之间的转换
  • 枚举值的迭代
  • 枚举值的范围检查
  • 枚举值的名称获取
  • 获取枚举值的索引
  • 获取枚举值的个数

2. 安装 Magic Enum

magic_enum 是一个仅包含头文件的库,因此安装非常简单。只需要将 magic_enum.hpp 文件包含到你的项目中即可。

#include <magic_enum/magic_enum.hpp>

如果需要高级功能,可能需要包含目录下更多头文件。

# 使用 vcpkg 安装(推荐)
vcpkg install magic-enum

# 使用 conan 安装
conan install magic_enum/0.8.2@

# 使用 GitHub 直接下载
git clone https://github.com/Neargye/magic_enum.git

3. 基本用法

3.1 枚举值与字符串的转换

magic_enum 提供了 enum_nameenum_cast 函数,用于在枚举值和字符串之间进行转换。

enum class Color { RED, GREEN, BLUE };

// 枚举值转字符串
std::string color_name = magic_enum::enum_name(Color::RED); // "RED"

// 字符串转枚举值
auto color = magic_enum::enum_cast<Color>("GREEN"); // Color::GREEN

// enum_cast 返回的是 std::optional<T>

if (auto color = magic_enum::enum_cast<Color>("GREEN")) {
std::cout << "转换成功: " << magic_enum::enum_name(*color) << std::endl;
} else {
std::cout << "转换失败" << std::endl;
}

3.2 枚举值的迭代

你可以使用 magic_enum::enum_values 函数来获取枚举类型的所有值,并进行迭代。

for (auto color : magic_enum::enum_values<Color>()) {
std::cout << magic_enum::enum_name(color) << std::endl;
}

3.3 枚举值的范围检查

magic_enum 还提供了 enum_contains 函数,用于检查某个值是否在枚举类型的范围内。

bool is_valid = magic_enum::enum_contains<Color>(2); // true
bool is_invalid = magic_enum::enum_contains<Color>(10); // false

// 这个函数也支持字符串检查
bool valid_name = magic_enum::enum_contains<Color>("RED"); // true
bool invalid_name = magic_enum::enum_contains<Color>("YELLOW"); // false

3.4 获取枚举值的名称

你可以使用 magic_enum::enum_names 函数来获取枚举类型的所有名称。

for (auto name : magic_enum::enum_names<Color>()) {
std::cout << name << std::endl;
}

magic_enum 还提供了 enum_entries,它返回 (枚举值, 名称) 的键值对,可以用来更方便地打印所有的映射关系:

for (const auto& [value, name] : magic_enum::enum_entries<Color>()) {
std::cout << static_cast<int>(value) << " -> " << name << std::endl;
}

3.5 获取枚举值的索引

magic_enum 提供了 enum_index 函数,用于获取枚举值的索引。

if (auto index = magic_enum::enum_index(Color::GREEN)) {
std::cout << "索引: " << *index << std::endl;
} else {
std::cout << "该值不在索引列表中" << std::endl;
}

3.6 获取枚举值的个数

你可以使用 magic_enum::enum_count 函数来获取枚举类型的值的个数。

auto count = magic_enum::enum_count<Color>(); // 3

4 高级用法

4.1 自定义枚举值的范围

magic_enum 默认解析的枚举值范围是 [-128, 128],这是由 MAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX 这两个宏定义决定的。如果你的枚举值超出了这个范围,就需要手动调整,否则 magic_enum 可能无法正确解析你的枚举。

示例:调整解析范围

假设我们有一个枚举,它的值范围较大:

enum class LargeEnum { MIN_VALUE = -300, VALUE1 = -200, VALUE2 = 100, MAX_VALUE = 300 };

默认情况下,magic_enum::enum_values<LargeEnum>() 只能解析 [-128, 128] 范围内的值,因此它不会正确解析 LargeEnum::MIN_VALUELargeEnum::MAX_VALUE

我们可以在 包含 magic_enum.hpp 之前 重新定义范围:

#define MAGIC_ENUM_RANGE_MIN -400
#define MAGIC_ENUM_RANGE_MAX 400
#include <magic_enum.hpp>

enum class LargeEnum { MIN_VALUE = -300, VALUE1 = -200, VALUE2 = 100, MAX_VALUE = 300 };

int main() {
for (auto value : magic_enum::enum_values<LargeEnum>()) {
std::cout << magic_enum::enum_name(value) << std::endl;
}
}

运行结果:

MIN_VALUE
VALUE1
VALUE2
MAX_VALUE

如果我们 不修改默认的范围,那么 MIN_VALUEMAX_VALUE 就不会出现在 enum_values<LargeEnum>() 里。

如何检查当前的枚举范围

你可以用 magic_enum::enum_range<T>() 来获取当前 magic_enum 解析的最小值和最大值:

constexpr auto range = magic_enum::enum_range<LargeEnum>();
std::cout << "magic_enum 解析范围: [" << range.min << ", " << range.max << "]" << std::endl;

默认输出(未修改范围时):

magic_enum 解析范围: [-128, 128]

修改 MAGIC_ENUM_RANGE_MIN/MAX 之后:

magic_enum 解析范围: [-400, 400]

这样你可以确认 magic_enum 解析的值是否在你需要的范围内。

注意事项

  1. MAGIC_ENUM_RANGE_MIN/MAX 需要在 #include <magic_enum.hpp> 之前定义,否则不会生效。
  2. 范围设得太大会影响编译性能,因为 magic_enum 需要检查更大的值空间。建议尽量只设定合适的范围。

4.2 处理不连续的枚举值

magic_enum 也支持不连续的枚举值。你可以通过 magic_enum::enum_values 函数获取所有有效的枚举值。

enum class Status { OK = 0, ERROR = 2, UNKNOWN = 5 };

for (auto status : magic_enum::enum_values<Status>()) {
std::cout << magic_enum::enum_name(status) << std::endl;
}

4.3 enum_flags_name(用于位运算枚举)

magic_enum 也支持 位运算枚举,如果使用 enum class 来表示位标志(Bitmask),那么 magic_enum::enum_flags_name 是非常有用的:

enum class Permissions { Read = 1, Write = 2, Execute = 4 };

Permissions userPerm = Permissions::Read | Permissions::Write;
std::cout << "用户权限: " << magic_enum::enum_flags_name(userPerm) << std::endl;
// 输出: "Read|Write"

// 遍历用户拥有的权限
for (auto perm : magic_enum::enum_values<Permissions>()) {
if (static_cast<int>(userPerm) & static_cast<int>(perm)) {
std::cout << "拥有权限: " << magic_enum::enum_name(perm) << std::endl;
}
}

#include <magic_enum/bitwise_operators.hpp>

Permissions p = Permissions::Read | Permissions::Write;
p &= Permissions::Read; // 现在 p 只有 Read 权限
std::cout << magic_enum::enum_flags(p) << std::endl; // "Read"

4.4 enum_type_name

如果想获取某个 枚举类型的名称,可以使用 magic_enum::enum_type_name<T>()

std::string type_name = magic_enum::enum_type_name<Color>(); // "Color"

4.5 magic_enum::ostream_operators(自动格式化输出)

如果想让 std::cout 自动支持输出枚举,可以使用 magic_enum::ostream_operators,这样 std::cout << Color::RED; 就会自动输出 "RED"

#include <magic_enum/magic_enum_iostream.hpp>

std::cout << Color::RED << std::endl; // 自动输出 "RED"

5. 主要限制

尽管 magic_enum 功能强大,但它也有一些限制:

  1. C++17 及以上版本magic_enum 需要 C++17 或更高版本的支持。
  2. 枚举值的范围magic_enum 默认处理的枚举值范围是 [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX],默认值为 [-128, 128]。如果枚举值超出这个范围,需要手动调整 MAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX 宏。
  3. 枚举值的连续性magic_enum::enum_values<T>() 可以处理不连续的枚举值,但 magic_enum::enum_index(T value) 可能不会返回连续的索引,而是基于 enum_values<T>() 里的顺序。
  4. 枚举值的唯一性magic_enum 允许重复的枚举值,但 enum_name(T value) 只会返回第一个匹配到的名称,而不会列出所有可能的名称。此外,如果 enum_name(T value) 传入的 value 不在 enum_values<T>() 列表里,它会返回空字符串 "",因此在使用时应检查返回值是否为空。

6. 总结

magic_enum 是 C++ 开发者处理枚举的 必备工具,它不仅简化了枚举的转换和遍历,还提升了代码的可读性和维护性。如果你在项目中频繁使用枚举类型,那么 magic_enum 绝对是一个 值得尝试 的库!

7. 参考链接

8. Demo

// 编译命令: g++ -std=c++17 magic_enum_demo.cpp -o magic_enum_demo -I.

#include <iostream>
#include <magic_enum/magic_enum_all.hpp>

// 定义普通枚举
enum class Color { RED, GREEN, BLUE };
enum class Status { OK = 0, ERROR = 2, UNKNOWN = 5 };

// 定义不连续枚举
enum class LargeEnum { MIN_VALUE = -300, VALUE1 = -200, VALUE2 = 100, MAX_VALUE = 300 };

// 定义带位运算的枚举
enum class Permissions { Read = 1, Write = 2, Execute = 4 };
using namespace magic_enum::bitwise_operators;

int main() {
std::cout << "===== 1. 枚举值与字符串转换 =====\n";
std::cout << "Color::RED -> " << magic_enum::enum_name(Color::RED) << "\n";

if (auto color = magic_enum::enum_cast<Color>("GREEN")) {
std::cout << "enum_cast(\"GREEN\") -> " << magic_enum::enum_name(*color) << "\n";
} else {
std::cout << "转换失败\n";
}

std::cout << "\n===== 2. 枚举值迭代 =====\n";
for (auto color : magic_enum::enum_values<Color>()) {
std::cout << magic_enum::enum_name(color) << " ";
}
std::cout << "\n";

std::cout << "\n===== 3. 枚举值范围检查 =====\n";
std::cout << "enum_contains(2): " << magic_enum::enum_contains<Color>(2) << "\n";
std::cout << "enum_contains(\"YELLOW\"): " << magic_enum::enum_contains<Color>("YELLOW") << "\n";

std::cout << "\n===== 4. 获取所有枚举值名称 =====\n";
for (auto name : magic_enum::enum_names<Color>()) {
std::cout << name << " ";
}
std::cout << "\n";

std::cout << "\n===== 5. 获取 (枚举值, 名称) 对 =====\n";
for (const auto& [value, name] : magic_enum::enum_entries<Color>()) {
std::cout << static_cast<int>(value) << " -> " << name << "\n";
}

std::cout << "\n===== 6. 获取枚举值索引 =====\n";
if (auto index = magic_enum::enum_index(Color::GREEN)) {
std::cout << "Color::GREEN 的索引: " << *index << "\n";
}

std::cout << "\n===== 7. 获取枚举值个数 =====\n";
std::cout << "Color 枚举值个数: " << magic_enum::enum_count<Color>() << "\n";

std::cout << "\n===== 8. 处理不连续的枚举值 =====\n";
for (auto status : magic_enum::enum_values<Status>()) {
std::cout << magic_enum::enum_name(status) << "\n";
}

std::cout << "\n===== 9. 处理位运算枚举 =====\n";
Permissions userPerm = Permissions::Read | Permissions::Write;
std::cout << "用户权限: " << magic_enum::enum_flags_name(userPerm) << "\n";

std::cout << "遍历用户权限:\n";
for (auto perm : magic_enum::enum_values<Permissions>()) {
if (static_cast<int>(userPerm) & static_cast<int>(perm)) {
std::cout << "拥有权限: " << magic_enum::enum_name(perm) << "\n";
}
}

Permissions p = Permissions::Read | Permissions::Write;
p &= Permissions::Read;
std::cout << "更新后权限: " << magic_enum::enum_name(p) << "\n";

std::cout << "\n===== 10. 获取枚举类型的名称 =====\n";
std::cout << "Color 类型名称: " << magic_enum::enum_type_name<Color>() << "\n";

using namespace magic_enum::ostream_operators;
std::cout << "\n===== 11. 直接输出枚举的名称 =====\n";
std::cout << Color::RED << std::endl; // 自动输出 "RED"

return 0;
}

===== 1. 枚举值与字符串转换 =====
Color::RED -> RED
enum_cast("GREEN") -> GREEN

===== 2. 枚举值迭代 =====
RED GREEN BLUE

===== 3. 枚举值范围检查 =====
enum_contains(2): 1
enum_contains("YELLOW"): 0

===== 4. 获取所有枚举值名称 =====
RED GREEN BLUE

===== 5. 获取 (枚举值, 名称) 对 =====
0 -> RED
1 -> GREEN
2 -> BLUE

===== 6. 获取枚举值索引 =====
Color::GREEN 的索引: 1

===== 7. 获取枚举值个数 =====
Color 枚举值个数: 3

===== 8. 处理不连续的枚举值 =====
OK
ERROR
UNKNOWN

===== 9. 处理位运算枚举 =====
用户权限: Read|Write
遍历用户权限:
拥有权限: Read
拥有权限: Write
更新后权限: Read

===== 10. 获取枚举类型的名称 =====
Color 类型名称: Color

===== 11. 直接输出枚举的名称 =====
RED