默认比较 (自 C++20 起)
比较运算符函数可以显式地默认为请求编译器为类生成相应的默认比较。
内容 |
[编辑] 定义
默认比较运算符函数 是一个非模板比较运算符函数(即 <=>
、==
、!=
、<
、>
、<=
或 >=
),满足以下所有条件
- 它是一个 非静态成员 或某个类
C
的 友元。 - 它是在
C
中或C
在 完整 的上下文中 定义为默认 的。 - 它有两个类型为 const C& 的参数或两个类型为
C
的参数,其中 隐式对象参数 (如果有)被认为是第一个参数。
这样的比较运算符函数被称为类 C
的默认比较运算符函数。
struct X { bool operator==(const X&) const = default; // OK bool operator==(const X&) = default; // Error: the implicit object // parameter type is X& bool operator==(this X, X) = default; // OK }; struct Y { friend bool operator==(Y, Y) = default; // OK friend bool operator==(Y, const Y&) = default; // Error: different parameter types }; bool operator==(const Y&, const Y&) = default; // Error: not a friend of Y
在比较运算符函数的隐式定义中执行的名称查找和访问检查从与其函数体等效的上下文中执行。在类中出现的默认比较运算符函数的定义必须是该函数的第一个声明。
[编辑] 默认比较顺序
给定一个类 C
,一个子对象列表按以下主题的顺序形成
C
的直接基类子对象,按声明顺序。C
的非静态 数据成员,按声明顺序。
- 如果任何成员子对象是数组类型,则将其展开为其元素的序列,按递增下标顺序。展开是递归的:数组类型中数组元素将再次展开,直到没有数组类型的子对象。
对于任何类型为 C
的对象 x,在以下描述中
- 令 n 为 x 的(扩展)子对象列表中的子对象数量。
- 令 x_i 为 x 的(扩展)子对象列表中的第 i 个子对象,其中 x_i 是由一系列 派生到基类的转换、类成员访问表达式 和 数组下标表达式 应用于 x 形成的。
struct S {}; struct T : S { int arr[2][2]; } t; // The subobject list for “t” consists of the following 5 subobjects in order: // (S)t → t[0][0] → t[0][1] → t[1][0] → t[1][1]
[编辑] 三方比较
可以将类类型的 operator<=> 定义为默认的,并具有任何返回类型。
[编辑] 比较类别类型
有三种比较类别类型
类型 | 等效值是.. | 不可比较的值是.. |
---|---|---|
std::strong_ordering | 无法区分 | 不允许 |
std::weak_ordering | 可区分 | 不允许 |
std::partial_ordering | 可区分 | 允许 |
[编辑] 合成三方比较
类型 T
之间相同类型的 glvalue a 和 b 的 _合成三方比较_ 定义如下
- 如果 a <=> b 的重载解析得到一个可用候选,并且可以使用
static_cast
显式转换为T
,则合成比较为 static_cast<T>(a <=> b). - 否则,如果满足以下任何条件,则合成比较未定义
- a <=> b 的重载解析找到至少一个可行候选。
-
T
不是比较类别类型。 - a == b 的重载解析没有得到可用候选。
- a < b 的重载解析没有得到可用候选。
- 否则,如果
T
是 std::strong_ordering,则合成比较为
a == b ? std::strong_ordering::equal : a < b ? std::strong_ordering::less : std::strong_ordering::greater
- 否则,如果
T
是 std::weak_ordering,则合成比较为
a == b ? std::weak_ordering::equivalent : a < b ? std::weak_ordering::less : std::weak_ordering::greater
- 否则 (
T
是 std::partial_ordering),则合成比较为
a == b ? std::partial_ordering::equivalent : a < b ? std::partial_ordering::less : b < a ? std::partial_ordering::greater : std::partial_ordering::unordered
[编辑] 占位符返回类型
如果类类型 C
的默认三方比较运算符函数 (operator<=>) 的声明返回类型为 auto,则从类型为 C
的对象 x 的对应子对象的三个三方比较的返回类型中推断出返回类型。
对于 x 的 (扩展) 子对象列表 中的每个子对象 x_i
- 执行 x_i <=> x_i 的重载解析,如果重载解析没有得到可用候选,则默认的 operator<=> 被定义为删除。
- 将 x_i <=> x_i 的类型的 cv 无限定版本表示为
R_i
,如果R_i
不是比较类别类型,则默认的 operator<=> 被定义为删除。
如果默认的 operator<=> 未定义为删除,则其返回类型被推断为 std::common_comparison_category_t<R_1, R_2, ..., R_n>.
[编辑] 非占位符返回类型
如果默认的 operator<=> 的声明返回类型不是 auto,则它不能包含任何 占位符类型 (例如 decltype(auto))。
如果在 x 的(扩展)子对象列表中存在一个子对象 x_i,使得 x_i 和 x_i 之间的声明返回类型的 合成三方比较 未定义,则默认的 operator<=> 被定义为删除。
[编辑] 比较结果
令 x 和 y 为默认的 operator<=> 的参数,将 x 和 y 的(扩展)子对象列表中的每个子对象分别表示为 x_i 和 y_i。默认情况下,x 和 y 之间的三方比较是通过比较对应子对象 x_i 和 y_i 以递增的 i 顺序进行的。
令 R
为(可能推断出的)返回类型,x_i 和 y_i 之间的比较结果是类型为 R
的 x_i 和 y_i 之间的合成三方比较的结果。
- 在 x 和 y 之间的默认三方比较期间,如果 x_i 和 y_i 之间的逐个子对象比较生成一个结果 v_i,使得将 v_i != 0 上下文转换为 bool 产生 true,则返回值是 v_i 的副本(不会比较剩余的子对象)。
- 否则,返回值为 static_cast<R>(std::strong_ordering::equal).
#include <compare> #include <iostream> #include <set> struct Point { int x; int y; auto operator<=>(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{1, 1}, pt2{1, 2}; std::set<Point> s; // OK s.insert(pt1); // OK // two-way comparison operator functions are not required to be explicitly defined: // operator== is implicitly declared (see below) // the overload resolutions of other candidates will select rewritten candidates std::cout << std::boolalpha << (pt1 == pt2) << ' ' // false << (pt1 != pt2) << ' ' // true << (pt1 < pt2) << ' ' // true << (pt1 <= pt2) << ' ' // true << (pt1 > pt2) << ' ' // false << (pt1 >= pt2) << ' '; // false }
[编辑] 相等性比较
[编辑] 显式声明
可以将类类型的 operator== 定义为默认的,并具有返回类型 bool。
给定一个类 C
和一个类型为 C
的对象 x,如果在 x 的(扩展)子对象列表中存在一个子对象 x_i,使得 x_i == x_i 的重载解析没有得到可用候选,则默认的 operator== 被定义为删除。
令 x 和 y 为默认的 operator== 的参数,将 x 和 y 的(扩展)子对象列表中的每个子对象分别表示为 x_i 和 y_i。默认情况下,x 和 y 之间的相等性比较是通过比较对应子对象 x_i 和 y_i 以递增的 i 顺序进行的。
x_i 和 y_i 之间的比较结果是 x_i == y_i 的结果。
- 在 x 和 y 之间的默认相等性比较期间,如果 x_i 和 y_i 之间的逐个子对象比较生成一个结果 v_i,使得将 v_i 上下文转换为 bool 产生 false,则返回值为 false(不会比较剩余的子对象)。
- 否则,返回值为 true.
#include <iostream> struct Point { int x; int y; bool operator==(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{3, 5}, pt2{2, 5}; std::cout << std::boolalpha << (pt1 != pt2) << '\n' // true << (pt1 == pt1) << '\n'; // true struct [[maybe_unused]] { int x{}, y{}; } p, q; // if (p == q) {} // Error: operator== is not defined }
[编辑] 隐式声明
如果一个类C
没有明确声明任何名为operator==的成员或友元,则会为每个定义为默认的operator<=>隐式声明一个运算符函数。每个隐式声明的operator==具有与相应的默认operator<=>相同的访问权限和函数定义,并且位于相同的类作用域中,但有以下更改
- 将声明符标识符替换为operator==.
- 将返回类型替换为bool.
template<typename T> struct X { friend constexpr std::partial_ordering operator<=>(X, X) requires (sizeof(T) != 1) = default; // implicitly declares: friend constexpr bool operator==(X, X) // requires (sizeof(T) != 1) = default; [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default; // implicitly declares: [[nodiscard]] virtual bool // operator==(const X&) const = default; };
[编辑] 次要比较
可以为类类型定义一个次要比较运算符函数(!=
、<
、>
、<=
或 >=
),其返回类型为bool。
设@
为五个次要比较运算符之一,对于每个默认的operator@,其参数为x 和 y,将执行最多两次重载解析(不将默认的operator@ 视为候选对象)以确定它是否被定义为删除。
- 第一次重载解析针对x @ y 执行。如果重载解析没有得到可用的候选对象,或者所选的候选对象不是重写候选对象,则默认的operator@ 将被定义为删除。在这些情况下,不会进行第二次重载解析。
- 第二次重载解析针对x @ y 的所选重写候选对象执行。如果重载解析没有得到可用的候选对象,则默认的operator@ 将被定义为删除。
如果x @ y 无法隐式转换为bool,则默认的operator@ 将被定义为删除。
如果默认的operator@ 没有被定义为删除,则它将产生x @ y。
struct HasNoRelational {}; struct C { friend HasNoRelational operator<=>(const C&, const C&); bool operator<(const C&) const = default; // OK, function is defaulted };
[编辑] 关键字
[编辑] 缺陷报告
以下行为变更缺陷报告已追溯应用于之前发布的 C++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
CWG 2539 | C++20 | 合成的三路比较将选择 static_cast 即使显式转换不可用 |
不选择 static_cast 在这种情况下 |
CWG 2546 | C++20 | 默认的次要operator@ 未 如果x @ y 的重载解析选择了不可用的重写候选对象,则定义为删除 定义为删除 |
在这种情况下 CWG 2547 |
尚不清楚比较运算符 | C++20 | 非类的函数是否可以设置为默认 它们不能设置为默认 |
CWG 2568 |
比较运算符的隐式定义 | C++20 | 函数可能违反成员访问规则 访问检查会从 |
等效于其函数体的上下文执行 等效于其函数体的上下文执行 等效于其函数体的上下文执行 |