Namespaces
Variants

Default comparisons (since C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

比较运算符函数可以显式默认化,以请求编译器为类生成相应的默认比较实现。

目录

定义

一个 默认比较运算符函数 是满足以下所有条件的非模板比较运算符函数(即 <=> == != < > <= >= ):

此类比较运算符函数被称为 C 的默认比较运算符函数

struct X
{
    bool operator==(const X&) const = default; // 正确
    bool operator==(const X&) = default;       // 错误:隐式对象参数类型为 X&
                                               //        与显式参数类型不匹配
    bool operator==(this X, X) = default;      // 正确
};
struct Y
{
    friend bool operator==(Y, Y) = default;        // 正确
    friend bool operator==(Y, const Y&) = default; // 错误:参数类型不一致
};
bool operator==(const Y&, const Y&) = default;     // 错误:非 Y 的友元函数

比较运算符函数隐式定义中的名称查找和访问检查,其执行上下文等效于其函数体内部。在类中作为默认项声明的比较运算符函数定义,必须是该函数的首次声明。

默认比较顺序

给定一个类 C ,子对象列表按以下顺序由下列子对象构成:

  • C 的直接基类子对象,按声明顺序排列。
  • C 的非静态 数据成员 ,按声明顺序排列。
  • 若任何成员子对象为数组类型,则将其展开为其元素的序列,按下标递增顺序排列。该展开是递归的:数组类型的数组元素将再次展开,直至不存在数组类型的子对象。

对于任何类型为 C 的对象 x ,以下描述中:

struct S {};
struct T : S
{
    int arr[2][2];
} t;
// 对象“t”的子对象列表按顺序包含以下5个子对象:
// (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 在相同类型的泛左值 a b 之间的 合成三路比较 按以下方式定义:

  • 如果对 a <=> b 的重载解析产生可用候选,且能通过 static_cast 显式转换为 T ,则合成比较结果为 static_cast < T > ( a <=> b )
  • 否则,若满足以下任一条件,则合成比较未定义:
  • a <=> b 的重载解析找到至少一个可行候选函数。
  • T 不是比较类别类型。
  • a == b 的重载解析未产生可用候选函数。
  • a < b 的重载解析未产生可用候选函数。
a == b ? std::strong_ordering::equal :
a < b  ? std::strong_ordering::less :
         std::strong_ordering::greater
a == b ? std::weak_ordering::equivalent :
a < b  ? std::weak_ordering::less :
         std::weak_ordering::greater
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

  1. x_i <=> x_i 执行重载决议,若重载决议未能产生可用候选函数,则默认的 operator <=> 被定义为删除。
  2. 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_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;
    /* 非比较函数 */
};
int main()
{
    Point pt1{1, 1}, pt2{1, 2};
    std::set<Point> s; // 正确
    s.insert(pt1);     // 正确
    // 无需显式定义双向比较运算符函数:
    // operator== 被隐式声明(见下文)
    // 其他候选函数的重载决议将选择重写的候选函数
    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 及其类型对象 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 经上下文转换到 bool 后得到 false ,则返回值为 false (剩余子对象将不再进行比较)。
  • 否则,返回值为 true
#include <iostream>
struct Point
{
    int x;
    int y;
    bool operator==(const Point&) const = default;
    /* 非比较函数 */
};
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) {} // 错误:未定义 operator==
}

隐式声明

如果类 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;
    // 隐式声明:friend constexpr bool operator==(X, X)
    //                   requires (sizeof(T) != 1) = default;
    [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default;
    // 隐式声明:[[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; // 正确:函数被默认实现
};

关键词

default

缺陷报告

下列行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。

缺陷报告 应用于 发布时的行为 正确行为
CWG 2539 C++20 合成的三路比较会选择
static_cast ,即使显式转换不可用
在此情况下不选择
static_cast
CWG 2546 C++20 默认的次要 operator@
x @ y 的重载解析选择了
不可用的重写候选时未被定义为删除
在此情况下
定义为删除
CWG 2547 C++20 未明确非类的比较运算符
函数是否可默认生成
不可默认生成
CWG 2568 C++20 比较运算符函数的隐式定义
可能违反成员访问规则
访问检查在其函数体
等效的上下文中执行

参见