C++ 基于operator<为enum class隐式生成比较运算符的实现方案
简化enum class重复比较运算符的实现方案
针对仅operator<包含实际逻辑、其余5个运算符全是固定复用的冗余场景,完全可以通过公共复用逻辑减少重复代码,具体方案分两种场景适配:
C++20及以上场景:零冗余原生实现
从C++20开始,编译器内置了基于三向比较的运算符自动生成机制,不需要手写任何重复的运算符代码,只需要在枚举定义后加一行默认三向比较的声明,就会自动生成全部6种比较运算符,生成逻辑和之前手写的实现完全等价:
enum class HandType : char { HIGH_CARD = 0, PAIR = 1, TWO_PAIR = 2, SET = 3, STRAIGHT = 4, FLUSH = 5, FULL_HOUSE = 6, QUADS = 7, STRAIGHT_FLUSH = 8, ROYAL_FLUSH = 9 }; // 仅需这一行代码,<, >, ==, !=, >=, <= 全部自动生成 constexpr auto operator<=>(HandType, HandType) = default;
编译器对枚举类型生成的默认三向比较,会直接基于枚举的底层存储值做对比,和原有手写逻辑没有任何性能或行为差异。
C++11/17兼容场景的通用模板实现
如果项目尚未升级到C++20,可以通过带SFINAE约束的通用模板,把所有重复的运算符逻辑抽成一次性编写的公共实现,做到所有枚举类型自动复用,不需要为每个enum class重复写5个冗余函数:
- 首先写一套全局生效的枚举比较模板,自动为所有枚举类型生成按底层值比较的
operator<:#include <type_traits> // 仅匹配枚举类型,自动生成operator< template <typename E> constexpr std::enable_if_t<std::is_enum_v<E>, bool> operator<(E a, E b) { using underlying_t = std::underlying_type_t<E>; return static_cast<underlying_t>(a) < static_cast<underlying_t>(b); } - 再配套一套通用的运算符模板,只要类型实现了
operator<,就自动生成剩余的5个比较运算符:// 自动基于operator<推导生成所有配套比较运算符 template <typename T> auto operator>(T a, T b) -> decltype(b < a) { return b < a; } template <typename T> auto operator==(T a, T b) -> decltype(!(a < b) && !(b < a)) { return !(a < b) && !(b < a); } template <typename T> auto operator!=(T a, T b) -> decltype(!(a == b)) { return !(a == b); } template <typename T> auto operator>=(T a, T b) -> decltype(a > b || a == b) { return a > b || a == b; } template <typename T> auto operator<=(T a, T b) -> decltype(a < b || a == b) { return a < b || a == b; }
这套实现的优势:
- 把上述模板放在项目公共头文件中,所有新定义的
enum class都会自动获得全部6种比较运算符,彻底消除每个枚举重复写冗余代码的工作量 - 内置的SFINAE约束保证只有合法实现了
operator<的类型才会匹配这套模板,不会和第三方类型的现有运算符、自定义特殊比较逻辑发生冲突 - 如果某个枚举需要特殊的比较规则(比如不是简单对比底层枚举值),只需要单独为这个枚举手写对应的
operator<,剩下的5个运算符依然会被通用模板自动生成,灵活性完全不受影响
适配注意事项
- 放置通用模板时,要保证参数依赖查找(ADL)能正常识别,不要把模板放在会导致查找失败的深层嵌套命名空间中
- 自定义的非模板运算符优先级高于通用模板,即便某个枚举定义了特殊比较逻辑,也不会和通用模板产生重载二义性
内容的提问来源于stack exchange,提问作者J. Grohmann
相关产品推荐
相关产品推荐

