Сравнение по умолчанию (начиная с 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
[править] Смотрите также
- Разрешение перегрузки в вызове перегруженного оператора
- Встроенный оператор трёхстороннего сравнения
- Перегрузка операторов для операторов сравнения