默认比较 (自 C++20 起)
比较运算符函数可以显式地声明为 defaulted,以请求编译器为类生成相应的默认比较。
目录 |
[编辑] 定义
默认比较运算符函数是一个非模板比较运算符函数 (即 <=>
, ==
, !=
, <
, >
, <=
, 或 >=
),满足以下所有条件
- 它是某个类
C
的 非静态成员或友元。 - 它在
C
中或在C
是完整类型的上下文中被定义为 defaulted。 - 它有两个类型为 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
比较运算符函数的隐式定义中的名称查找和访问检查是从等同于其函数体的上下文中执行的。在类中出现的比较运算符函数作为 defaulted 的定义必须是该函数的第一个声明。
[编辑] 默认比较顺序
给定一个类 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<=> 可以被定义为 defaulted,并带有任何返回类型。
[编辑] 比较类别类型
有三种比较类别类型
类型 | 等效值是.. | 不可比较的值是.. |
---|---|---|
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<=> 定义为 deleted。
- 将 x_i <=> x_i 的类型的 cv-unqualified 版本表示为
R_i
,如果R_i
不是比较类别类型,则默认的 operator<=> 定义为 deleted。
如果默认的 operator<=> 未定义为 deleted,则其返回类型推导为 std::common_comparison_category_t<R_1, R_2, ..., R_n>。
[编辑] 非占位符返回类型
如果默认的 operator<=> 的声明返回类型不是 auto,则它不能包含任何占位符类型(例如 decltype(auto))。
如果 x 的(展开的)子对象列表中存在子对象 x_i,使得声明的返回类型在 x_i 和 x_i 之间的合成三路比较未定义,则默认的 operator<=> 定义为 deleted。
[编辑] 比较结果
设 x 和 y 是默认的 operator<=> 的参数,将 x 和 y 的(展开的)子对象列表中的每个子对象分别表示为 x_i 和 y_i。 x 和 y 之间的默认三路比较是通过按递增的 i 顺序比较相应的子对象 x_i 和 y_i 来执行的。
设 R
为(可能推导出的)返回类型,x_i 和 y_i 之间的比较结果是 x_i 和 y_i 之间类型 R
的合成三路比较的结果。
- 在 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== 可以被定义为 defaulted,返回类型为 bool。
给定类 C
和类型为 C
的对象 x,如果 x 的(展开的)子对象列表中存在子对象 x_i,使得 x_i == x_i 的重载解析没有产生可用的候选者,则默认的 operator== 定义为 deleted。
设 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== 的成员或友元,则为每个定义为 defaulted 的 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; };
[编辑] 二级比较
类类型的二级比较运算符函数 (!=
, <
, >
, <=
, 或 >=
) 可以被定义为 defaulted,返回类型为 bool。
设 @
为五个二级比较运算符之一,对于每个参数为 x 和 y 的默认 operator@
,最多执行两次重载解析(不将默认的 operator@
视为候选者)以确定它是否定义为 deleted。
- 第一次重载解析对 x @ y 执行。如果重载解析没有产生可用的候选者,或者选择的候选者不是rewritten candidate,则默认的
operator@
定义为 deleted。在这些情况下,没有第二次重载解析。 - 第二次重载解析对 x @ y 的选定 rewritten candidate 执行。如果重载解析没有产生可用的候选者,则默认的
operator@
定义为 deleted。
如果 x @ y 不能隐式转换为 bool,则默认的 operator@
定义为 deleted。
如果默认的 operator@
未定义为 deleted,则它产生 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 的重载解析 选择了一个不可用的 rewritten candidate,则被定义为 deleted |
被定义为 deleted 在这种情况下 |
CWG 2547 | C++20 | 不清楚比较运算符 用于非类的函数是否可以 defaulted |
它们不能被 defaulted |
CWG 2568 | C++20 | 比较运算符的隐式定义 函数可能违反成员访问规则 |
执行访问检查 从等效的上下文中 到它们的函数体 |