c++|C++20三路比较运算符


C++20的三路比较运算符 operator<=>

    • 01 默认比较
      • 01.01 默认比较
      • 01.02 定制比较
        • 强序
        • 弱序
        • 偏序
    • 02 C++20的关系运算符与比较接口
    • 03 参考

三路比较运算符 <=> 通常被称为宇宙飞船运算符(spaceship operator)。可以执行字典序比较,它按照基类从左到右的顺序,并按字段声明顺序对非静态成员进行比较。
在类ClassName中预置 <=> 运算符 auto operator<=>(const ClassName&) const = default; 后,编译器会生成全部共六个比较运算符,如 ==、!=、<、<=、> 和 >= 。

01 默认比较 01.01 默认比较
默认比较1提供一种方式,以要求编译器为某个类生成相一致的关系运算符。
简言之,定义了 operator<=> 的类自动获得由编译器生成的运算符 ==、!=、<、<=、> 和 >=。类可以将 operator<=> 定义为预置的,这种情况下编译器亦将为该运算符生成代码
class Point { int x; int y; public: Point(int a, int b) :x(a), y(b) {} auto operator<=>(const Point&) const = default; // 预置 operator<=> // 因为预置 operator<=>,现在能用 ==、!=、<、<=、> 和 >= 比较 Point // ……非比较函数…… }; void test_compare01() { // 编译器生成全部四个关系运算符 Point pt1{ 1, 2 }, pt2{1, 3}; std::set s; // OK s.insert(pt1); // OK s.insert(pt2); if (pt1 <= pt2) { // OK,只调用一次 <=> std::cout << "pt1 <= pt2\n"; } else { std::cout << "! (pt1 <= pt2)\n"; } }

01.02 定制比较
如果预置的语义不适合,例如在必须不按各成员的顺序进行比较,或必须用某种不同于它们的自然比较操作的比较,这种情况下程序员可以编写 operator<=> 并令编译器生成适合的关系运算符。生成的关系运算符种类取决于用户定义 operator<=> 的返回类型。
【c++|C++20三路比较运算符】有三种可用的返回类型:
返回类型 运算符 等价的值…… 不可比较的值……
(强序)std::strong_ordering == != < > <= >= 不可区分 不允许存在
(弱序)std::weak_ordering == != < > <= >= 可区分 不允许存在
(偏序)std::partial_ordering == != < > <= >= 可区分 允许存在
强序 一个返回 std::strong_ordering 的定制 operator<=> 的例子是,对类的每个成员进行比较的运算符,但与默认的顺序有所不同(此处为姓优先)。
注意:返回 std::strong_ordering 的运算符应该对所有成员都进行比较,因为遗漏了任何成员都将会损害可替换性:二个比较相等的值可能变得可以区分。
class Base { public: auto operator<=>(const Base&) const = default; }; std::strong_ordering operator <=>(const std::string& a, const std::string& b) { int cmp = a.compare(b); if (cmp < 0) return std::strong_ordering::less; else if (cmp > 0) return std::strong_ordering::greater; else return std::strong_ordering::equivalent; } class TotallyOrdered : Base { std::string tax_id; std::string first_name; std::string last_name; public: TotallyOrdered(const std::string& id, const std::string& first, const std::string& last) :tax_id(id), first_name(first), last_name(last) {} // 定制 operator<=>,因为我们想先比较姓 std::strong_ordering operator<=>(const TotallyOrdered& that) const { if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0) return cmp; if (auto cmp = last_name <=> that.last_name; cmp != 0) return cmp; if (auto cmp = first_name <=> that.first_name; cmp != 0) return cmp; return tax_id <=> that.tax_id; } // ……非比较函数…… }; void test_compare02() { // 编译器生成全部四个关系运算符 TotallyOrdered to1{ "1", "first1", "last1" }, to2{ "2", "first2", "last2" }; std::set s; // ok s.insert(to1); // ok s.insert(to2); if (to1 <= to2) { // ok,调用一次 <=> std::cout << "to1 <= to2\n"; } else { std::cout << "!(to1 <= to2)\n"; } }

弱序 一个返回 std::weak_ordering 的定制 operator<=> 的例子是,以大小写无关方式比较类的字符串成员的运算符:这不同于默认比较(故要求定制运算符),并且在这种比较下有可能对比较相等的两个字符串加以区分。
注意,此示例演示了异质 operator<=> 所具有的效果:它在两个方向都生成了异质的比较。
class CaseInsensitiveString { std::string s; std::weak_ordering case_insensitive_compare( const char* a, const char* b) const { int cmp = _stricmp(a, b); if (cmp < 0) return std::weak_ordering::less; else if (cmp > 0) return std::weak_ordering::greater; else return std::weak_ordering::equivalent; } public: CaseInsensitiveString(const std::string& str) : s(str) {} std::weak_ordering operator<=>(const CaseInsensitiveString& b) const { return case_insensitive_compare(s.c_str(), b.s.c_str()); } std::weak_ordering operator<=>(const char* b) const { return case_insensitive_compare(s.c_str(), b); } // ……非比较函数…… }; void test_compare03() { // 编译器生成全部四个关系运算符 CaseInsensitiveString cis1{ "XYzza" }, cis2{ "xyzza" }; std::set s; // ok s.insert(cis1); // ok s.insert(cis2); if (cis1 <= cis2) {// ok,进行一次比较运算 std::cout << "cis1 <= cis2\n"; } // 编译器亦生成全部八个异相关系运算符 if (cis1 <= "xyzzy") {// ok,进行一次比较运算 std::cout << "cis1 <= \"xyzzy\"\n"; } if ("xyzzy" >= cis1) {// ok,等同的语义 std::cout << "\"zyzzy\" >= cis1\n"; } }

偏序 偏序是允许存在不可比较(无序)值的排序,例如浮点排序中的 NaN 值,或这个例子中的无关人员:
class PersonInFamilyTree { private: int parent_family_level_id = -1; int self_family_level_id = -1; bool is_the_same_person_as(const PersonInFamilyTree& rhs) const { return (self_family_level_id >= 0 && (self_family_level_id == rhs.self_family_level_id)); } bool is_transitive_child_of(const PersonInFamilyTree& rhs) const { return (rhs.self_family_level_id >= 0 && (parent_family_level_id == rhs.self_family_level_id)); } public: PersonInFamilyTree(int parent, int self) : parent_family_level_id(parent), self_family_level_id(self) {} std::partial_ordering operator<=>(const PersonInFamilyTree& that) const { if (this->is_the_same_person_as(that)) return std::partial_ordering::equivalent; if (this->is_transitive_child_of(that)) return std::partial_ordering::less; if (that.is_transitive_child_of(*this)) return std::partial_ordering::greater; return std::partial_ordering::unordered; } // ……非比较函数…… }; void test_compare04() { // 编译器生成全部四个关系运算符 PersonInFamilyTree per1{ 0, 1 }, per2{ 1, 10 }; if (per1 < per2) { std::cout << "ok, per2 是 per1 的祖先\n"; } else if (per1 > per2) { std::cout << "ok, per1 是 per2 的祖先\n"; } else if (std::is_eq(per1 <=> per2)) { std::cout << "ok, per1 即是 per2\n"; } else { std::cout << "per1 与 per2 无关\n"; } if (per1 <= per2) { std::cout << "ok, per2 是 per1 或 per1 的祖先\n"; } if (per1 >= per2) { std::cout << "ok, per1 是 per2 或 per2 的祖先\n"; } if (std::is_neq(per1 <=> per2)) { std::cout << "ok, per1 不是 per2\n"; } }

02 C++20的关系运算符与比较接口 关系运算符与比较2。定义于头文件
方法 说明
three_way_comparable
three_way_comparable_with
指定运算符 <=> 在给定类型上产生一致的结果
partial_ordering 三路比较的结果类型,支持所有 6 种运算符,不可替换,并允许不可比较的值
weak_ordering 三路比较的结果类型,支持所有 6 种运算符且不可替换
strong_ordering 三路比较的结果类型,支持所有 6 种运算符且可替换
is_eq
is_neq
is_lt
is_lteq
is_gt
is_gteq
具名比较函数
compare_three_way 实现 x <=> y 的函数对象
compare_three_way_result 获得三路比较运算符 <=> 在给定类型上的结果
common_comparison_category 给定的全部类型都能转换到的最强比较类别
strong_order 进行三路比较并产生 std::strong_ordering 类型结果
weak_order 进行三路比较并产生 std::weak_ordering 类型结果
partial_order 进行三路比较并产生 std::partial_ordering 类型结果
compare_strong_order_fallback 进行三路比较并产生 std::strong_ordering 类型的结果,即使 operator<=> 不可用
compare_weak_order_fallback 进行三路比较并产生 std::weak_ordering 类型的结果,即使 operator<=> 不可用
compare_partial_order_fallback 进行三路比较并产生 std::partial_ordering 类型的结果,即使 operator<=> 不可用
03 参考 A: 微软博客Simplify Your Code With Rocket Science: C++20’s Spaceship Operator
B: 比较运算符(https://zh.cppreference.com/w/cpp/language/operator_comparison)
C: operator<=> for C++20入门篇
D: 文中代码https://github.com/5455945/cpp_demo/blob/master/C%2B%2B20/compare/compare.cpp
E: Visual Studio 2019 版本 16.4 的符合性改进
  1. 默认比较(https://zh.cppreference.com/w/cpp/language/default_comparisons) ??
  2. 关系运算符与比较(https://zh.cppreference.com/w/cpp/utility) ??

    推荐阅读