Spec-Zone.ru › C++

Сравнения по умолчанию (начиная с C++20)

Функции операторов сравнения можно явно объявить как defaulted, чтобы запросить у компилятора генерацию соответствующего сравнения по умолчанию для класса.

Определение

Функция оператора сравнения, объявленная как defaulted — это нетипизированная функция оператора сравнения (то есть <=>, ==, !=, <, >, <= или >=), удовлетворяющая всем следующим условиям:

  • Она является нестатическим членом или дружественной функцией некоторого класса C.
  • Она определена как defaulted в C или в контексте, в котором C является полным типом.
  • У неё есть два параметра типа constC& или два параметра типа C, причём неявный параметр объекта (если он есть) считается первым параметром.

Такая функция оператора сравнения называется функцией оператора сравнения, объявленной как defaulted, для класса 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 в порядке объявления.
    • Если какой-либо подобъект-член имеет тип массива, он разворачивается в последовательность своих элементов в порядке возрастания индексов. Разворачивание выполняется рекурсивно: элементы массива, имеющие тип массива, разворачиваются снова, пока не останется подобъектов типа массива.

Для любого объекта x типа C в следующих описаниях:

  • Пусть n — количество подобъектов в (развёрнутом) списке подобъектов для x.
  • Пусть x_i — i-й подобъект в (развёрнутом) списке подобъектов для x, где 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
Тип Эквивалентные значения... Несравнимые значения...
std::strong_ordering неразличимы не допускаются
std::weak_ordering различимы не допускаются
std::partial_ordering различимы допускаются

Синтезированное трёхстороннее сравнение

Синтезированное трёхстороннее сравнение типа T между glvalue-значениями a и b одного типа определяется следующим образом:

  • Если разрешение перегрузки для a <=> b приводит к пригодному кандидату, который можно явно преобразовать в T с помощью static_cast, синтезированное сравнение равно 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

Возвращаемый тип-заполнитель

Если объявленный возвращаемый тип функции трёхстороннего сравнения (operator<=>), объявленной как defaulted для типа класса C, — это auto, возвращаемый тип выводится из возвращаемых типов трёхсторонних сравнений соответствующих подобъектов объекта x типа C.

Для каждого подобъекта x_i в (развёрнутом) списке подобъектов для x:

  1. Выполнить разрешение перегрузки для x_i <=> x_i; если разрешение перегрузки не приводит к пригодному кандидату, объявленный как defaulted operator<=> определяется как удалённый.
  2. Обозначить версию типа x_i <=> x_i без cv-квалификаторов как R_i; если R_i не является типом категории сравнения, объявленный как defaulted operator<=> определяется как удалённый.

Если объявленный как defaulted operator<=> не определён как удалённый, его возвращаемый тип выводится как std::common_comparison_category_t<R_1,R_2,...,R_n>.

Возвращаемый тип, не являющийся типом-заполнителем

Если объявленный возвращаемый тип объявленного как defaulted operator<=> не является auto, он не может содержать тип-заполнитель (например, decltype(auto)).

Если в (развёрнутом) списке подобъектов для x имеется подобъект x_i, для которого синтезированное трёхстороннее сравнение объявленного возвращаемого типа между x_i и x_i не определено, объявленный как defaulted operator<=> определяется как удалённый.

Результат сравнения

Пусть x и y — параметры объявленного как defaulted 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== для типа класса можно определить как defaulted с возвращаемым типом bool.

Для класса C и объекта x типа C, если в (развёрнутом) списке подобъектов для x имеется подобъект x_i, для которого разрешение перегрузки для x_i == x_i не приводит к пригодному кандидату, объявленный как defaulted operator== определяется как удалённый.

Пусть x и y — параметры объявленного как defaulted 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<=>, определённого как defaulted, неявно объявляется функция-оператор. Каждая неявно объявленная функция operator== имеет тот же уровень доступа и определение функции, что и соответствующий operator<=>, объявленный как defaulted, и находится в той же области видимости класса, со следующими изменениями:

  • Идентификатор декларатора заменяется на 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.

Пусть @ — один из пяти вторичных операторов сравнения. Для каждого объявленного как defaulted оператора@ с параметрами x и y выполняется до двух разрешений перегрузки (без учёта объявленного как defaulted оператора@ в качестве кандидата), чтобы определить, должен ли он быть определён как удалённый.

  • Первое разрешение перегрузки выполняется для x @ y. Если разрешение перегрузки не приводит к пригодному кандидату или выбранный кандидат не является переписанным кандидатом, объявленный как defaulted оператор@ определяется как удалённый. В этих случаях второе разрешение перегрузки не выполняется.
  • Второе разрешение перегрузки выполняется для выбранного переписанного кандидата x @ y. Если разрешение перегрузки не приводит к пригодному кандидату, объявленный как defaulted оператор@ определяется как удалённый.

Если x @ y нельзя неявно преобразовать в bool, объявленный как defaulted оператор@ определяется как удалённый.

Если объявленный как defaulted оператор@ не определён как удалённый, он возвращает x @ y.

struct HasNoRelational {};

struct C
{
    friend HasNoRelational operator<=>(const C&, const C&);
    bool operator<(const C&) const = default; // OK, function is defaulted
};

Ключевые слова

default

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.

DR Применяется к Опубликованное поведение Корректное поведение
CWG 2539 C++20 синтезированное трёхстороннее сравнение выбирало
static_cast, даже если явное преобразование недоступно
в этом случае не выбирает
static_cast
CWG 2546 C++20 объявленный как defaulted вторичный оператор@ не
определялся как удалённый, если разрешение перегрузки для
x @ y выбирало непригодного переписанного кандидата
в этом случае определяется
как удалённый
CWG 2547 C++20 не было ясно, можно ли объявлять функции операторов сравнения
для типов, не являющихся классами, как defaulted
их нельзя объявлять как defaulted
CWG 2568 C++20 неявное определение функций операторов сравнения
могло нарушать правила доступа к членам
проверки доступа выполняются
из контекста, эквивалентного
телам этих функций

См. также

  • Разрешение перегрузки при вызове перегруженного оператора
  • Встроенный оператор трёхстороннего сравнения
  • Перегрузка операторов для операторов сравнения

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/default_comparisons

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API