默认比较 (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 | 可区分的 | 允许 |
[编辑] 合成三路比较
相同类型的 glvalue a 和 b 之间的类型 T
的合成三路比较定义如下:
- 如果 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 之间的默认三路比较通过按递增 i 顺序比较相应的子对象 x_i 和 y_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 之间的默认相等比较通过按递增 i 顺序比较相应的子对象 x_i 和 y_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 的默认化。
令 @
为五个次要比较运算符之一,对于每个带有参数 x 和 y 的默认化 operator@,最多执行两次重载决议(不将默认化的 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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 2539 | C++20 | 合成三路比较将选择 static_cast 即使显式转换不可用 |
不选择 在这种情况下使用 static_cast |
CWG 2546 | C++20 | 默认化的次要 operator@ 没有 如果 x @ y 的重载决议 选择了不可用的重写候选,则定义为已删除。 |
定义为已删除 在这种情况下 |
CWG 2547 | C++20 | 尚不清楚非类类型的比较运算符 函数是否可以默认化 |
它们不能默认化 |
CWG 2568 | C++20 | 比较运算符函数的隐式定义 可能违反成员访问规则 |
执行访问检查 从与它们的函数体 等效的上下文进行 |