std::to_underlying 是 c++23 引入的标准函数,c++20 及更早版本不支持;若编译器或标准库未更新(如 gcc

to_underlying 是 C++23 标准函数,C++20 及更早版本没有
直接说结论:std::to_underlying 是 C++23 引入的,如果你用的是 GCC 12、Clang 15 或 MSVC 19.33 之前的编译器,默认不支持;开了 -std=c++23 也不一定行,得看标准库实现是否跟上。
常见错误现象:error: 'to_underlying' is not a member of 'std' —— 这不是你写错了,是编译器/标准库压根没提供。
- 确认编译器和标准库版本:GCC 12+ 配 libstdc++ 12+、Clang 15+ 配 libc++ 15+ 才有完整支持
- 检查是否启用了 C++23:
-std=c++23(GCC/Clang),MSVC 用/std:c++23 - 别依赖头文件包含关系:
to_underlying在<utility></utility>中,但即使包含了也没用——没实现就是没实现
没有 to_underlying 时怎么安全转枚举值
核心原则:不能直接用 static_cast<int>(e)</int>,尤其当枚举底层类型不是 int 或有负值时,行为可能不符合预期。
推荐写法是显式取出底层类型再 cast:
立即学习“C++免费学习笔记(深入)”;
template <typename E>
constexpr std::underlying_type_t<E> to_underlying(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e);
}
这个模板和标准版语义一致,且兼容所有 C++11+ 编译器。
- 必须用
std::underlying_type_t<e></e>,而不是硬写int—— 枚举可能定义为enum class E : char或unsigned short - 加上
noexcept和constexpr,保持和标准版一样的约束条件 - 不要用宏模拟,宏无法推导模板参数,也破坏类型安全
为什么不能直接 static_cast(e)?
因为枚举的底层类型由编译器决定,或由声明时的 : T 显式指定。一旦底层类型不是 int,比如是 uint8_t,强转成 int 会多一次符号扩展或截断,影响位运算、序列化、网络传输等场景。
典型出问题的场景:
- 把枚举值写进二进制文件,读出来发现高位全 1(
enum class E : int8_t { A = -1 };→static_cast<int>(A)</int>得 -1,但按字节存其实是0xFF,还原时若误当uint8_t就变 255) - 跨平台通信中,不同编译器对未指定底层类型的枚举选型不同(Clang 偏好最小有符号类型,MSVC 偏好
int) - 使用
std::bit_cast或 memcpy 做类型双关时,底层类型不匹配直接 UB
枚举转换时容易被忽略的底层细节
真正麻烦的从来不是“怎么转”,而是“转完之后怎么用”。很多 bug 出现在后续操作中对底层值的假设上。
-
enum class的底层类型在 ODR 使用前不强制确定,sizeof(E)可能在不同 TU 中不同(虽然罕见,但标准允许) - 空枚举(无枚举器)如
enum class E {};,std::underlying_type_t<e></e>合法,但to_underlying无法调用——没实例可传 - 如果枚举含非连续值(如
A=1, B=1000),底层类型可能比你预期的大(例如从int8_t升到int16_t),影响结构体大小和缓存对齐
实际项目里,建议把枚举转底层值的操作收拢到一个内联函数里,加注释说明用途(比如“仅用于日志打印”或“用于 protobuf 序列化”),避免后续维护者误以为这是通用转换入口。










