前言:为什么需要三路比较? 写 C++ 代码时,你一定遇到过这种痛苦:
1 2 3 4 5 6 7 8 9 10 11 struct Point { int x, y; bool operator ==(const Point& o) const { return x == o.x && y == o.y; } bool operator !=(const Point& o) const { return !(*this == o); } bool operator <(const Point& o) const { return x < o.x || (x == o.x && y < o.y); } bool operator <=(const Point& o) const { return *this < o || *this == o; } bool operator >(const Point& o) const { return !(*this <= o); } bool operator >=(const Point& o) const { return !(*this < o); } };
C++20 的 <=> 运算符(三路比较 / spaceships operator)彻底解决了这个问题 :你只需要写一个 运算符,编译器自动生成其他五个!
一、运算符 <=> 是什么? 1.1 三路比较的原理 传统的两路比较:a < b 返回 bool 三路比较:a <=> b 返回一个比较结果对象 ,这个对象可以与 0 比较,判断 a < b、a == b 或 a > b。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <compare> int main () { int a = 1 , b = 2 ; auto result = a <=> b; if (result < 0 ) { std::cout << "a < b\n" ; } else if (result > 0 ) { std::cout << "a > b\n" ; } else { std::cout << "a == b\n" ; } }
输出 :a < b
1.2 为什么叫”三路”? 因为 <=> 同时表达了三种关系:
就像一个三分量开关 ,而不是二分的开关。
二、比较类别(Comparison Categories) C++20 在 <compare> 头文件中定义了三种比较结果类型,分别对应不同的相等性语义:
类型 含义 等价关系 示例 std::strong_ordering强顺序,值唯一 == 必须严格相等int, Pointstd::weak_ordering弱顺序,值可不等价但可比 == 可不严格相等忽略大小写的字符串 std::partial_ordering部分顺序,允许无序 多了 unordered 浮点数(NaN)
2.1 strong_ordering 适用场景 :值是唯一可识别 的,== 意味着完全相等 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <compare> struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; int main () { Point p1{1 , 2 }, p2{1 , 2 }, p3{1 , 3 }; static_assert (std::same_as< decltype (p1 <=> p2), std::strong_ordering >); std::cout << (p1 == p2) << "\n" ; std::cout << (p1 < p3) << "\n" ; std::cout << (p1 > p3) << "\n" ; }
2.2 weak_ordering 适用场景 :值可以不可区分但仍可比 ,== 不要求严格相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <compare> #include <string> struct CaseInsensitive { std::string text; auto operator <=>(const CaseInsensitive& o) const { return std::lexicographical_compare_three_way ( text.begin (), text.end (), o.text.begin (), o.text.end (), [](char a, char b) { return std::tolower (a) <=> std::tolower (b); } ); } bool operator ==(const CaseInsensitive& o) const { return std::ranges::equal (text, o.text, [](char a, char b) { return std::tolower (a) == std::tolower (b); }); } };
2.3 partial_ordering 适用场景 :存在无序值 (如 NaN)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <compare> #include <cmath> int main () { double a = 1.0 , b = 2.0 , nan1 = std::nan ("" ), nan2 = std::nan ("" ); auto cmp = a <=> b; static_assert (std::same_as<decltype (cmp), std::partial_ordering>); std::cout << (cmp < 0 ) << "\n" ; auto cmp_nan = nan1 <=> nan2; std::cout << (cmp_nan == 0 ) << "\n" ; std::cout << (cmp_nan < 0 ) << "\n" ; std::cout << (cmp_nan > 0 ) << "\n" ; std::cout << std::is_eq (cmp_nan) << "\n" ; std::cout << std::is_lt (cmp_nan) << "\n" ; std::cout << std::is_gt (cmp_nan) << "\n" ; std::cout << std::is_unordered (cmp_nan) << "\n" ; }
三、= default:让编译器代劳 3.1 自动生成所有比较运算符 C++20 最强大的功能:只需要 = default 一个运算符,编译器自动生成其他五个!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <compare> #include <vector> #include <algorithm> struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; struct User { std::string name; int age; auto operator <=>(const User&) const = default ; }; int main () { Point p1{1 , 2 }, p2{1 , 3 }; auto cmp = p1 <=> p2; if constexpr (cmp < 0 ) { std::cout << "p1 < p2\n" ; } else if (cmp > 0 ) { std::cout << "p1 > p2\n" ; } else { std::cout << "p1 == p2\n" ; } if (p1 <=> p2 == 0 ) { std::cout << "equal\n" ; } std::vector<Point> pts{{3 ,1 }, {1 ,1 }, {2 ,2 }}; std::sort (pts.begin (), pts.end ()); for (auto & p : pts) { std::cout << "(" << p.x << "," << p.y << ") " ; } std::cout << "\n" ; }
输出 :
1 2 3 p1 < p2 equal (1,1) (2,2) (3,1)
3.2 = delete:禁用某些比较 如果不想支持某种比较,直接 delete:
1 2 3 4 5 6 7 8 9 struct NotComparable { int value; auto operator <=>(const NotComparable&) const = delete ; }; static_assert (NotComparable{1 } == NotComparable{1 });
四、a <=> b == 0 的语义 这是一个强大的 idiom :三路比较的结果可以直接与 0 比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <compare> int main () { int a = 5 , b = 10 ; if (a <=> b == 0 ) { std::cout << "equal\n" ; } else { std::cout << "not equal\n" ; } if (a <=> b < 0 ) { std::cout << "a < b\n" ; } if (a <=> b >= 0 ) { std::cout << "a >= b\n" ; } }
原理 :比较运算符返回的类别对象重载了与 0 的比较。
五、与标准库的集成 5.1 std::sort 自动使用 <=> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <vector> #include <algorithm> struct Item { int priority; std::string name; auto operator <=>(const Item&) const = default ; }; int main () { std::vector<Item> items{ {3 , "low" }, {1 , "high" }, {2 , "medium" } }; std::sort (items.begin (), items.end ()); for (auto & item : items) { std::cout << item.priority << ": " << item.name << "\n" ; } }
输出 :
1 2 3 1: high 2: medium 3: low
5.2 associative containers 自动使用 <=> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <map> #include <set> struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; int main () { std::set<Point> pts{{1 ,2 }, {3 ,4 }, {1 ,2 }}; std::cout << pts.size () << "\n" ; std::map<Point, int > m; m[{1 ,2 }] = 100 ; m[{3 ,4 }] = 200 ; std::cout << m[{1 ,2 }] << "\n" ; }
六、比较流程图 flowchart TD
START(["🚀 编译器遇到 a <=> b"]) --> TYPE{"类型推断"}
TYPE -->|"强顺序<br/>(strong_ordering)"| A["🔵 值唯一<br/>必须精确相等"]
TYPE -->|"弱顺序<br/>(weak_ordering)"| B["🟣 值可不等价<br/>但仍可比"]
TYPE -->|"部分顺序<br/>(partial_ordering)"| C["🟡 存在无序值<br/>如 NaN"]
A --> RESULT1["返回 strong_ordering:<br/>• equivalent = (v == 0)<br/>• less = (v < 0)<br/>• greater = (v > 0)"]
B --> RESULT2["返回 weak_ordering:<br/>• equivalent = (v == 0)<br/>• less = (v < 0)<br/>• greater = (v > 0)"]
C --> RESULT3["返回 partial_ordering:<br/>• equivalent = (v == 0)<br/>• less = (v < 0)<br/>• greater = (v > 0)<br/>• unordered = (v != v)"]
style START fill:#C7CEEA,stroke:#9FA8DA,color:#333
style A fill:#B5EAD7,stroke:#80CBC4,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333七、完整编译示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <compare> #include <vector> #include <algorithm> struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; struct User { std::string name; int age; auto operator <=>(const User&) const = default ; }; int main () { Point p1{1 , 2 }, p2{1 , 3 }; auto cmp = p1 <=> p2; if constexpr (cmp < 0 ) std::cout << "p1 < p2\n" ; else if (cmp > 0 ) std::cout << "p1 > p2\n" ; else std::cout << "p1 == p2\n" ; if (p1 <=> p2 == 0 ) std::cout << "equal\n" ; std::vector<Point> pts{{3 ,1 }, {1 ,1 }, {2 ,2 }}; std::sort (pts.begin (), pts.end ()); double a = 1.0 , b = 2.0 ; auto pcmp = a <=> b; (void )pcmp; }
运行结果 :
八、注意事项 8.1 默认生成的比较顺序 对于 struct,默认按成员声明顺序 比较:
1 2 3 4 5 6 struct Employee { std::string name; int id; auto operator <=>(const Employee&) const = default ; };
8.2 自定义比较顺序 可以指定比较的优先级:
1 2 3 4 5 6 7 8 9 struct Item { int priority; std::string name; auto operator <=>(const Item& o) const { if (auto cmp = priority <=> o.priority; cmp != 0 ) return cmp; return name <=> o.name; } };
九、总结 特性 说明 operator<=>三路比较运算符,返回比较结果对象 = default编译器自动生成所有比较运算符 = delete禁用特定比较 a <=> b == 0等价于 a == b strong_ordering强顺序,值唯一(int、Point) weak_ordering弱顺序,可不严格相等(忽略大小写) partial_ordering部分顺序,有无序值(浮点数 NaN)
记住 :<=> 不是用来替代 == 的,而是用来统一实现 比较运算的。一行 = default,六种比较能力,何乐而不为?
📚 C++20 新特性 系列导航 本文是《C++20 新特性》系列第 3/7 篇。
📖 全部 7 篇目录(点击展开) (一)Concepts (二)requires 表达式 (三)Spaceship Operator ← 当前 (四)Lambda 加强 (五)consteval 与 constinit (六)Coroutine 协程 (七)Ranges 库