C++20三路比较运算符


三路比较运算符 <=> 通常被称为宇宙飞船运算符(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<Point> 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<=> 的返回类型。

有三种可用的返回类型:

返回类型运算符等价的值……不可比较的值……
(强序)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<TotallyOrdered> 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<CaseInsensitiveString> 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。定义于头文件 <compare>

方法说明
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) ↩︎

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值