Сравнения по умолчанию (начиная с 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 | различимы | допускаются |
Синтезированное трёхстороннее сравнение
Синтезированное трёхстороннее сравнение типа 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:
- Выполнить разрешение перегрузки для
x_i <=> x_i; если разрешение перегрузки не приводит к пригодному кандидату, объявленный как defaulted operator<=> определяется как удалённый. - Обозначить версию типа
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
}; Ключевые слова
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам 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