Spec-Zone.ru › C++

Стандартные сравнения (с 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)
оператор - оператор сравнения (<=>, ==, !=, <, >, <=, или >=)
тип_возврата - тип возвращаемого значения функции-оператора. Должен быть
  • auto или один из трёх типов категорий сравнения, если оператор это <=>,
  • в противном случае, bool

Объяснение

1) Объявление функции сравнения по умолчанию как члена класса.
2) Объявление функции сравнения по умолчанию как вне класса функции.
3) Объявление функции сравнения по умолчанию как вне класса функции. Аргументы передаются по значению.

Функция трёхстороннего сравнения (будь то по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с помощью <, >, <=, >=, или <=> и разрешение перегрузки выбирает эту перегрузку.

Функция сравнения на равенство (будь то по умолчанию или нет) вызывается всякий раз, когда значения сравниваются с помощью == или != и разрешение перегрузки выбирает эту перегрузку.

Как и функции-члены по умолчанию, функция сравнения по умолчанию определяется, если она используется или необходима для вычисления константного выражения.

Стандартные сравнения

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

Оператор по умолчанию <=> выполняет лексикографическое сравнение, последовательно сравнивая базовые (слева направо, в глубину) и затем нестатические члены (в порядке объявления) подобъекты 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

Spec-Zone.ru

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