Сравнение по умолчанию (начиная с 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) | |||||||
| оп | — | оператор сравнения (<=>, ==, !=, <, >, <= или >=)
|
| возвращаемый-тип | — | возвращаемый тип операторной функции. Должен быть
|
Объяснение
Функция трёхстороннего сравнения (независимо от того, используется она по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с использованием <, >, <=, >= или <=> и разрешение перегрузки выбирает эту перегрузку.
Функция сравнения на равенство (независимо от того, используется она по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с использованием == или !=, и разрешение перегрузки выбирает эту перегрузку.
Как и специальные функции-элементы по умолчанию, функция сравнения по умолчанию определяется, если используется odr или она необходима для константной оценки.
| Этот раздел не завершён Причина: упомянуть специальное правило разрешения перегрузки для операторов сравнения, добавленное в C++20 |
Сравнение по умолчанию
Трёхстороннее сравнение по умолчанию
operator<=> по умолчанию выполняет лексикографическое сравнение, последовательно сравнивая базовые (слева направо в глубину), а затем нестатические объекты элементы (в порядке объявления) T для вычисления <=>, рекурсивно расширяя элементы массива (в порядке возрастания индекса) и преждевременно останавливаясь при обнаружении неравного результата, то есть:
for /*каждый базовый объект или подобъект элемент o 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и обнаруживается хотя бы один жизнеспособный кандидат, сравнение не определяется (operator<=>определён как удалённый). - Иначе, если
Rне является типом категории сравнения (смотрите ниже), либоa == bилиa < bне используются, сравнение не определено (operator<=>определяется как удалённый). - Иначе, если
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
Согласно правилам для любой перегрузки operator<=>, перегрузка <=> по умолчанию также позволяет сравнивать тип с <, <=, > и >=.
Если operator<=> используется по умолчанию, а operator== вообще не объявлен, то operator== неявно используется по умолчанию.
#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; // OK
s.insert(pt1); // OK
std::cout << std::boolalpha
<< (pt1 == pt2) << ' ' // false; operator== неявно используется по умолчанию.
<< (pt1 != pt2) << ' ' // true
<< (pt1 < pt2) << ' ' // true
<< (pt1 <= pt2) << ' ' // true
<< (pt1 > pt2) << ' ' // false
<< (pt1 >= pt2) << ' ';// false
}
Сравнение на равенство по умолчанию
Класс может определить operator== как значение по умолчанию с возвращаемым значением bool. Это создаст сравнение на равенство каждого базового класса и подобъекта-элемента в порядке их объявления. Два объекта равны, если равны значения их базовых классов и элементов. Тест будет коротким замыканием, если неравенство обнаружется в элементах или базовых классах ранее в порядке объявления.
Согласно правилам для operator==, это также позволит проверить на неравенство:
#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==' не определён
}
Другие операторы сравнения по умолчанию
Любой из четырёх операторов сравнения может быть явно установлен по умолчанию. Оператор сравнения по умолчанию должен иметь возвращаемый тип bool.
Такой оператор будет удалён, если не удастся разрешить перегрузку x <=> y (учитывая также operator<=> с обратным порядком параметров), или если этот operator@ не применим к результату этого x <=> y. Иначе, operator@ по умолчанию вызывает x <=> y @ 0, если operator<=> с исходным порядком параметров был выбран разрешением перегрузки или 0 @ y <=> x иначе:
struct HasNoRelational {};
struct C {
friend HasNoRelational operator<=>(const C&, const C&);
bool operator<(const C&) const = default; // OK, функция по умолчанию
};
Точно так же operator!= можно использовать по умолчанию. Он удаляется, если не удаётся разрешить перегрузку x == y (учитывая также operator== с обратным порядком параметров) или если результат x == y не имеет тип bool. operator!= по умолчанию вызывает !(x == y) или !(y == x) в зависимости от разрешения перегрузки.
По умолчанию операторы отношения могут быть полезны для создания функций, чьи адреса могут быть взяты. Для других целей достаточно указать только operator<=> и operator==.
Пользовательские сравнения и категории сравнения
Когда семантика по умолчанию не подходит, например, когда элементы должны сравниваться не по порядку или должны использовать сравнение, отличное от их естественного сравнения, тогда программист может написать operator<=> и тогда компилятор сгенерирует соответствующие операторы двустороннего сравнения. Тип создаваемых операторов двустороннего сравнения зависит от типа возвращаемого значения определяемого пользователем operator<=>.
Доступны три возвращаемых типа:
| Возвращаемый тип | Эквивалентные значения.. | Несравнимые значения.. |
|---|---|---|
| std::strong_ordering | неразличимы | не разрешены |
| std::weak_ordering | различимы | не разрешены |
| std::partial_ordering | различимы | разрешены |
Строгий порядок
Примером пользовательского operator<=>, который возвращает 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:
// пользовательский operator<=>, потому что мы хотим сначала сравнить фамилии:
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;
}
// ... функции несравнения ...
};
// компилятор генерирует все четыре оператора отношения
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, один вызов <=>
}
Примечание: оператор, который возвращает std::strong_ordering, должен сравнивать каждый элемент, потому что, если какой-либо элемент будет опущен, заменяемость может быть нарушена: становится возможным различить два значения, которые при сравнении равны.
Слабый порядок
Примером пользовательского operator<=>, который возвращает 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);
}
// ... функции несравнения ...
};
// Компилятор генерирует все четыре оператора отношения
CaseInsensitiveString cis1, cis2;
std::set<CaseInsensitiveString> s; // OK
s.insert(/*...*/); // OK
if (cis1 <= cis2) { /*...*/ } // OK, выполняет одну операцию сравнения
// Компилятор также генерирует все восемь разнородных операторов отношения
if (cis1 <= "xyzzy") { /*...*/ } // OK, выполняет одну операцию сравнения
if ("xyzzy" >= cis1) { /*...*/ } // OK, идентичная семантика
Обратите внимание, что этот пример демонстрирует эффект гетерогенного operator<=>: он генерирует гетерогенные сравнения в обоих направлениях.
Частичный порядок
Частичное упорядочение это упорядочение, которое допускает несравнимые (неупорядоченные) значения, такие как значения 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;
}
// ... функции несравнения ...
};
// компилятор генерирует все четыре оператора отношения
PersonInFamilyTree per1, per2;
if (per1 < per2) { /*...*/ } // OK, per2 является предком per1
else if (per1 > per2) { /*...*/ } // OK, per1 является предком per2
else if (std::is_eq(per1 <=> per2)) { /*...*/ } // OK, per1 равен per2
else { /*...*/ } // per1 и per2 не связаны
if (per1 <= per2) { /*...*/ } // OK, per2 равен per1 или является предком per1
if (per1 >= per2) { /*...*/ } // OK, per1 равен per2 или является предком per2
if (std::is_neq(per1 <=> per2)) { /*...*/ } // OK, per1 не равен per2
Смотрите также
- Разрешение перегрузки в вызове перегруженного оператора
- Встроенный оператор трёхстороннего сравнения
- Перегрузка операторов для операторов сравнения