C++20的三路比较运算符 operator<=>
三路比较运算符
<=>
通常被称为宇宙飞船运算符(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 的符合性改进