Стандартные сравнения (с C++20)
Предоставляет способ запросить у компилятора генерацию согласованных операторов сравнения для класса.
Синтаксис
тип_возврата имя_класса ::operatorоператор(const имя_класса &) const &(необязательно) = default; | (1) | (с C++20) |
friend тип_возврата operatorоператор (const имя_класса &, const имя_класса &) = default; | (2) | (с C++20) |
friend тип_возврата operatorоператор (имя_класса , имя_класса ) = default; | (3) | (с C++20) |
тип_возврата имя_класса ::operatorоператор(this const имя_класса &, const имя_класса &) = default; | (4) | (с C++23) |
тип_возврата имя_класса ::operatorоператор (this имя_класса , имя_класса ) = default; | (5) | (с C++23) |
| оператор | - | оператор сравнения (<=>, ==, !=, <, >, <=, или >=) |
| тип_возврата | - | тип возвращаемого значения функции-оператора. Должен быть
|
Объяснение
Функция трёхстороннего сравнения (будь то по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с помощью <, >, <=, >=, или <=> и разрешение перегрузки выбирает эту перегрузку.
Функция сравнения на равенство (будь то по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с помощью == или != и разрешение перегрузки выбирает эту перегрузку.
Как и функции-члены по умолчанию, функция сравнения по умолчанию определяется, если она используется или необходима для вычисления константного выражения.
Стандартные сравнения
Стандартное трёхстороннее сравнение
Оператор по умолчанию <=> выполняет лексикографическое сравнение, последовательно сравнивая базовые (слева направо, в глубину) и затем нестатические члены (в порядке объявления) подобъекты T для вычисления <=>, рекурсивно расширяя массивные члены (в порядке возрастания индекса) и прекращая сравнение при обнаружении неравенства, а именно:
for /*each base or member subobject o of T*/
if (auto cmp = static_cast<R>(compare(lhs.o, rhs.o)); cmp != 0)
return cmp;
return static_cast<R>(strong_ordering::equal);Не определено, сравниваются ли подобъекты виртуального базового класса более одного раза.
Если объявленный тип возврата — auto, то фактический тип возврата — общая категория сравнения базового и членов под-объектов и членов массивов для сравнения (см. std::common_comparison_category). Это упрощает запись случаев, когда тип возврата нетривиально зависит от членов, например:
template<class T1, class T2>
struct P
{
T1 x1;
T2 x2;
friend auto operator<=>(const P&, const P&) = default;
};Пусть R — тип возврата, каждая пара под-объектов a, b сравнивается следующим образом:
- Если
a <=> bпригоден и может быть явным образом преобразован вRс помощьюstatic_cast, результат сравнения —static_cast<R>(a <=> b). - В противном случае, если разрешение перегрузки для
a <=> bвыполняется и находит хотя бы один жизнеспособный кандидат, сравнение не определено (оператор<=> определён как удалённый). - В противном случае, если
Rне является типом категории сравнения (см. ниже), или либоa == bилиa < bне пригоден, сравнение не определено (оператор<=> определён как удалённый). - В противном случае, если
R—std::strong_ordering, результат
a == b ? R::equal :
a < b ? R::less :
R::greater- В противном случае, если
R—std::weak_ordering, результат
a == b ? R::equivalent :
a < b ? R::less :
R::greater- В противном случае (
R—std::partial_ordering), результат
a == b ? R::equivalent :
a < b ? R::less :
b < a ? R::greater :
R::unorderedСогласно правилам для любого оператора <=>, стандартная перегрузка <=> также позволит сравнивать тип с <, <=, >, и >=.
Если оператор<=> стандартный и оператор== не объявлен вообще, то оператор== неявно стандартный.
#include <compare>
#include <iostream>
#include <set>
struct Point
{
int x;
int y;
auto operator<=>(const Point&) const = default;
// ... non-comparison functions ...
};
// compiler generates all six two-way comparison operators
int main()
{
Point pt1{1, 1}, pt2{1, 2};
std::set<Point> s; // OK
s.insert(pt1); // OK
std::cout << std::boolalpha
<< (pt1 == pt2) << ' ' // false; operator== is implicitly defaulted.
<< (pt1 != pt2) << ' ' // true
<< (pt1 < pt2) << ' ' // true
<< (pt1 <= pt2) << ' ' // true
<< (pt1 > pt2) << ' ' // false
<< (pt1 >= pt2) << ' '; // false
}Стандартное сравнение на равенство
Класс может определить оператор== как стандартный, с типом возврата bool. Это сгенерирует сравнение на равенство каждого базового класса и под-объектов, в порядке их объявления. Два объекта равны, если значения их базовых классов и членов равны. Проверка завершается немедленно, если обнаруживается неравенство в членах или базовых классах, расположенных ранее в порядке объявления.
Согласно правилам оператора ==, это также позволит проверять неравенство:
#include <iostream>
struct Point
{
int x;
int y;
bool operator==(const Point&) const = default;
// ... non-comparison functions ...
};
// compiler generates element-wise equality testing
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
}Другие стандартные операторы сравнения
Любой из четырёх операторов отношения может быть явным образом стандартным. Стандартный оператор отношения должен иметь тип возврата bool.
Такой оператор будет удалён, если разрешение перегрузки над x <=> y (с учётом также оператора <=> с обратным порядком параметров) потерпит неудачу или если данный оператор@ не применим к результату того x <=> y. В противном случае, стандартный оператор@ вызывает x <=> y @ 0 если оператор<=> с исходным порядком параметров был выбран разрешением перегрузки, или 0 @ y <=> x в противном случае:
struct HasNoRelational {};
struct C
{
friend HasNoRelational operator<=>(const C&, const C&);
bool operator<(const C&) const = default; // OK, function is defaulted
};Аналогично, оператор!= может быть стандартным. Он удаляется, если разрешение перегрузки над x == y (с учётом также оператора== с обратным порядком параметров) потерпит неудачу, или если результат x == y не имеет типа bool. Стандартный оператор!= вызывает !(x == y) или !(y == x) в зависимости от выбора разрешения перегрузки.
Стандартизация операторов отношения может быть полезна для создания функций, адреса которых можно взять. Для других целей достаточно предоставить только оператор<=> и оператор==.
Пользовательские сравнения и категории сравнения
Когда стандартная семантика не подходит, например, когда члены необходимо сравнивать в другом порядке или необходимо использовать сравнение, отличное от естественного, программист может написать оператор<=> и позволить компилятору сгенерировать соответствующие операторы двустороннего сравнения. Тип операторов двустороннего сравнения, сгенерированных компилятором, зависит от типа возврата пользовательского оператора<=>.
Доступны три типа возвращаемых значений:
| Тип возврата | Равные значения.. | Несравнимые значения.. |
|---|---|---|
std::strong_ordering | неразличимы | недопустимы |
std::weak_ordering | различимы | недопустимы |
std::partial_ordering | различимы | допустимы |
Строгое упорядочивание
Пример пользовательского оператора<=>, возвращающего std::strong_ordering, — это оператор, сравнивающий все члены класса, но в порядке, отличном от стандартного (здесь: фамилия прежде имени).
#include <cassert>
#include <compare>
#include <set>
#include <string>
struct Base
{
std::string zip;
auto operator<=>(const Base&) const = default;
};
struct TotallyOrdered : Base
{
std::string tax_id;
std::string first_name;
std::string last_name;
public:
// custom operator<=> because we want to compare last names first:
std::strong_ordering operator<=>(const TotallyOrdered& that) const
{
if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0)
return cmp;
if (auto cmp = last_name <=> that.last_name; cmp != 0)
return cmp;
if (auto cmp = first_name <=> that.first_name; cmp != 0)
return cmp;
return tax_id <=> that.tax_id;
}
// ... non-comparison functions ...
};
// compiler generates all four relational operators
int main()
{
TotallyOrdered to1{"a", "b", "c", "d"}, to2{"a", "b", "d", "c"};
std::set<TotallyOrdered> s; // OK
s.insert(to1); // OK
assert(to2 <= to1); // OK, single call to <=>
}Примечание: оператор, возвращающий std::strong_ordering, должен сравнивать все члены, поскольку пропуск любого из них может нарушить взаимозаменяемость: это позволит различать два значения, которые равны при стандартном сравнении.
Слабое упорядочение
Пример пользовательского оператора<=>, возвращающего std::weak_ordering, — это оператор, сравнивающий строковые члены класса без учёта регистра: это отличается от стандартного сравнения (поэтому требуется пользовательский оператор), и позволяет различать две строки, которые равны при стандартном сравнении:
class CaseInsensitiveString
{
std::string s;
public:
std::weak_ordering operator<=>(const CaseInsensitiveString& b) const
{
return case_insensitive_compare(s.c_str(), b.s.c_str());
}
std::weak_ordering operator<=>(const char* b) const
{
return case_insensitive_compare(s.c_str(), b);
}
// ... non-comparison functions ...
};
// Compiler generates all four relational operators
CaseInsensitiveString cis1, cis2;
std::set<CaseInsensitiveString> s; // OK
s.insert(/*...*/); // OK
if (cis1 <= cis2) { /*...*/ } // OK, performs one comparison operation
// Compiler also generates all eight heterogeneous relational operators
if (cis1 <= "xyzzy") { /*...*/ } // ok, performs one comparison operation
if ("xyzzy" >= cis1) { /*...*/ } // ok, identical semanticsОбратите внимание, что этот пример демонстрирует влияние гетерогенного оператора<=>: он генерирует гетерогенные сравнения в обоих направлениях.
Частичное упорядочение
Частичное упорядочение — это упорядочение, допускающее несравнимые (неупорядоченные) значения, такие как значения NaN в сравнении чисел с плавающей точкой, или, в этом примере, лица, не связанные между собой:
class PersonInFamilyTree // ...
{
public:
std::partial_ordering operator<=>(const PersonInFamilyTree& that) const
{
if (this->is_the_same_person_as(that))
return partial_ordering::equivalent;
if (this->is_transitive_child_of(that))
return partial_ordering::less;
if (that. is_transitive_child_of(*this))
return partial_ordering::greater;
return partial_ordering::unordered;
}
// ... non-comparison functions ...
};
// compiler generates all four relational operators
PersonInFamilyTree per1, per2;
if (per1 < per2) { /*...*/ } // OK, per2 is an ancestor of per1
else if (per1 > per2) { /*...*/ } // OK, per1 is an ancestor of per2
else if (std::is_eq(per1 <=> per2)) { /*...*/ } // OK, per1 is per2
else { /*...*/ } // per1 and per2 are unrelated
if (per1 <= per2) { /*...*/ } // OK, per2 is per1 or an ancestor of per1
if (per1 >= per2) { /*...*/ } // OK, per1 is per2 or an ancestor of per2
if (std::is_neq(per1 <=> per2)) { /*...*/ } // OK, per1 is not per2См. также
- разрешение перегрузки при вызове перегруженного оператора
- Встроенный трехсторонний оператор сравнения
- Перегрузка операторов для операторов сравнения
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/default_comparisons