C++20的宇宙飞船

C++添加了<=>运算符,当使用 a <=> b 时,它返回的不是一个简单的布尔值 (true/false) 或整数

它返回一个特殊的比较类别对象 (comparison category object)。这个对象封装了 ab 之间详细的排序关系。这些对象的类型都定义在 <compare> 头文件中。

最核心的返回类型有三种:

  1. std::strong_ordering (强有序)
  2. std::weak_ordering (弱有序)
  3. std::partial_ordering (偏序)

理解返回对象的本质:与 0 比较

理解这些返回对象最简单的方式,就是把它们想象成一个“黑盒”,你可以拿它和 0 进行比较。这个思想继承自C语言中 strcmp 等函数的设计。

a <=> b 的结果 res 可以这样来解读:

  • 如果 res < 0,意味着 a < b
  • 如果 res > 0,意味着 a > b
  • 如果 res == 0,意味着 ab 相等或等价

这就是为什么编译器可以基于 a <=> b 的结果自动生成 a < b ((a <=> b) < 0)、a > b ((a <=> b) > 0) 等操作的原因。


三种返回类型的详细说明

那么,为什么需要三种不同的类型呢?因为它们描述了不同层次的“相等”关系。

1. std::strong_ordering (强有序)

这是最严格、最常见的排序。

  • 核心思想:如果 ab 的比较结果是“相等”,那么它们就是完全等同、可以互相替换的。对 a 做任何操作的结果都应该和对 b 做同样操作的结果完全一样。
  • 返回的具名常量
    • std::strong_ordering::less (表示 a < b)
    • std::strong_ordering::equal (表示 ab 完全相等)
    • std::strong_ordering::greater (表示 a > b)
  • 典型例子
    • int 类型:5 <=> 10 返回 std::strong_ordering::less
    • 只包含整型成员的结构体。
#include <compare>
#include <iostream>

int main() {
    int a = 5, b = 10;
    std::strong_ordering result = (a <=> b);

    if (result == std::strong_ordering::less) {
        std::cout << "a is less than b" << std::endl;
    }
}

2. std::weak_ordering (弱有序)

  • 核心思想:如果 ab 的比较结果是“相等”,它们仅仅是排序等价 (equivalent),但本身不一定完全相同。
  • 返回的具名常量
    • std::weak_ordering::less
    • std::weak_ordering::equivalent (表示 ab 排序等价)
    • std::weak_ordering::greater
  • 典型例子
    • 不区分大小写的字符串比较:字符串 "apple""Apple" 在不区分大小写的排序中是“等价”的,但它们本身并不相等。
    • 一个只关心部分成员进行比较的类。
// 伪代码示例
// CaseInsensitiveString s1 = "apple";
// CaseInsensitiveString s2 = "Apple";
// std::weak_ordering result = (s1 <=> s2); // result 会是 std::weak_ordering::equivalent

3. std::partial_ordering (偏序)

  • 核心思想:它除了描述大小关系,还允许“无法比较 (unordered)”这种情况的存在。
  • 返回的具名常量
    • std::partial_ordering::less
    • std::partial_ordering::equivalent
    • std::partial_ordering::greater
    • std::partial_ordering::unordered (表示 ab 无法比较)
  • 典型例子
    • 浮点数 doublefloat:因为有 NaN (Not a Number) 的存在。任何数字与 NaN 的比较结果都是“无序”的。
    • 1.0 <=> 2.0 返回 std::partial_ordering::less
    • 1.0 <=> NAN 返回 std::partial_ordering::unordered

使用宇宙飞船运算符重载运算符

对于大多数简单的结构体或类,你甚至不需要自己实现 operator<=> 的函数体。你只需要告诉编译器使用默认版本

编译器会按照成员变量的声明顺序,依次对每个成员进行三路比较,一旦发现不相等就立即返回结果。

**示例:使用 defaultPoint

#include <iostream>
#include <compare> // 引入比较类别

class Point {
public:
    int x, y;

    // 魔法发生的地方!
    // 1. 默认生成三路比较,它会依次比较 x 和 y
    auto operator<=>(const Point& other) const = default;
    
    // 2. 默认生成相等比较(通常也建议默认化)
    bool operator==(const Point& other) const = default;
};

int main() {
    Point p1{1, 2};
    Point p2{1, 3};
    Point p3{2, 1};

    std::cout << std::boolalpha; // 让输出显示 true/false
    
    // 所有这些比较现在都可以直接工作了!
    std::cout << "p1 < p2: " << (p1 < p2) << std::endl;   // true
    std::cout << "p1 > p2: " << (p1 > p2) << std::endl;   // false
    std::cout << "p1 == p1: " << (p1 == p1) << std::endl; // true
    std::cout << "p1 != p2: " << (p1 != p2) << std::endl; // true
    std::cout << "p3 >= p1: " << (p3 >= p1) << std::endl; // true
}

编译器如何决定返回哪种类型?

当你为一个类使用 auto operator<=>(...) const = default; 时,编译器会检查类的所有成员:

  • 如果所有成员的 operator<=> 都返回 std::strong_ordering,那么最终结果就是 std::strong_ordering
  • 如果成员中至少有一个返回 std::weak_ordering(且没有返回 partial_ordering 的),那么最终结果就是 std::weak_ordering
  • 如果成员中至少有一个返回 std::partial_ordering,那么最终结果就是 std::partial_ordering

这就像一个“最弱一环”原则,返回类型由最不严格的那个成员比较来决定。auto 关键字在这里非常关键,它让编译器为你自动推断出正确的返回类型。

总结

返回类型 核心含义 "相等"的常量 "无序"的可能 典型用例
std::strong_ordering 完全相同,可替换 equal int, std::string
std::weak_ordering 排序上等价,但本身不一定相同 equivalent 不区分大小写的字符串
std::partial_ordering 可能存在无法比较的情况 equivalent (unordered) double, float (因NaN)

所以,a <=> b 返回的是一个信息丰富的类别对象,不仅返回你比较结果,还返回这个比较的性质(是强有序、弱有序还是偏序)。