C++compare_three_way三路比较器
C++20 里的“裁判员”:compare_three_way 不是语法糖,是新范式
你有没有写过这样的代码?
if (a < b) return -1;
else if (a > b) return 1;
else return 0;
或者更糟——自己手撸 operator<=> 却漏了 noexcept、忘了 = default 的语义差异、甚至把 strong_ordering 和 weak_ordering 混着用……
别笑,我去年在重构一个金融行情比对模块时,就因为没吃透 compare_three_way,导致两个浮点时间戳在 std::set 里反复插入失败——不是崩溃,是静默逻辑错:a == b 返回 true,但 a <=> b 却给出 std::partial_ordering::unordered。
这恰恰暴露了一个事实:compare_three_way 不是“多一种比较方式”的可选项,而是 C++20 为统一比较契约埋下的底层地基。
compare_three_way 是一个函数对象(struct compare_three_way { ... };),它不自己做比较,而是调度你类型已定义的 operator<=>。它的核心价值,藏在三个字里:三路。
不是布尔真假,不是单向大小,而是直接返回一个 ordering 类型:std::strong_ordering、std::weak_ordering 或 std::partial_ordering。每种都自带语义约束——比如 strong_ordering::equal 要求 a == b 为真且 a 与 b 可互换;而 partial_ordering::unordered 明确告诉你:“这两个值无法比较”,比如 NaN <=> 3.14。
这就绕开了老式 operator< 的致命短板:它只说“小于”,却闭口不谈“相等是否可判定”、“是否满足对称性”、“NaN 怎么办”。而 compare_three_way 强制你在类型设计阶段就回答这些问题。
怎么用?先看最典型的场景:容器排序。
以前写 std::sort(v.begin(), v.end()),它默认调 operator<。现在你可以显式传入:
std::sort(v.begin(), v.end(), std::compare_three_way{});
看起来只是加了个 {},但效果截然不同:它会尝试调用 operator<=>,失败才回落到 <。更重要的是,如果你的自定义类型重载了 operator<=> 并返回 std::weak_ordering,std::sort 就能正确处理等价但不相等的元素(比如忽略大小写的字符串比较)——而旧式 < 无法表达这种“等价关系”。
再看 std::map 的键比较。假设你有个结构体:
struct Point { double x, y; };
auto operator<=>(const Point& a, const Point& b) = default;
编译器生成的 operator<=> 是 strong_ordering,std::map<Point, int> 完全没问题。但如果你换成 std::complex<double> 呢?它没有 operator<=>,compare_three_way{} 就会编译失败——这反而是好事:它立刻提醒你,“复数天然不可全序”,硬塞进 map 是设计漏洞,该换 unordered_map 或自定义哈希。
最容易被忽略的细节:compare_three_way 不是万能胶水。
它只对支持三路比较的类型生效。比如 std::string_view 支持,std::vector<int> 支持(因 operator<=> 默认生成),但 std::unique_ptr<T> 不支持——它只有 <,没有 <=>。这时候 compare_three_way{} 会退回到 <,但你可能根本没意识到这个“降级”。解决办法很简单:
// 显式检查是否真正用了三路比较
static_assert(std::is_same_v<
decltype(std::compare_three_way{}(a, b)),
std::strong_ordering
>, "期望强序,但实际未启用 <=>");
这种静态断言,在 CI 流程里加一条,就能拦住未来同事的误用。
最后说个实战经验:别急着给所有类加 operator<=>。
曾有个同事给一个含 std::mutex 的类加上 = default 的 <=>,结果编译不过——因为 mutex 不可比较,编译器拒绝合成。他改成手动实现,却忘了 mutex 不能拷贝/移动,导致比较函数里隐式调用 operator=……最终死锁。
正确的做法是:只对真正需要参与通用比较逻辑的类型启用 <=>,且优先用 = default(安全、高效、语义清晰);若需定制,务必明确返回哪种 ordering,并用 noexcept 标注——因为 compare_three_way 的调度链要求整个比较路径无异常。
C++20 的 compare_three_way,表面是个函数对象,实则是语言层面对“比较”这件事的正交化重构。它不替代 <,而是逼你思考:这个类型,到底属于哪一类序?是强序(如整数)、弱序(如忽略大小写的字符串)、还是偏序(如浮点数)?
当你下次再写 if (a < b) 时,不妨停半秒:这里真的只需要“小于”吗?还是该让 a <=> b 直接说出全部真相?
毕竟,真正的工程稳健,从来不是靠更多 if-else 堆出来的,而是从类型契约的第一行就写清楚的。


还没有评论,来说两句吧...