Namespaces
Variants
Views
Actions

Difference between revisions of "cpp/language/default comparisons"

From cppreference.com
< cpp‎ | language
m (mention special overload resolution rule for comparison operators added in C++20?)
m (Minor fix.)
 
(17 intermediate revisions by 9 users not shown)
Line 2: Line 2:
 
{{cpp/language/expressions/navbar}}
 
{{cpp/language/expressions/navbar}}
  
Provides a way to request the compiler to generate consistent comparison operators for a class.
+
Comparison operator functions can be explicitly defaulted to request the compiler to generate the corresponding default comparison for a class.
  
===Syntax===
+
===Definition===
{{sdsc begin}}
+
A ''defaulted comparison operator function'' is a non-template comparison operator function (i.e. {{tt|1=<=>}}, {{tt|1===}}, {{tt|1=!=}}, {{tt|<}}, {{tt|>}}, {{tt|1=<=}}, or {{tt|1=>=}}) satisfying all following conditions:
{{sdsc | num=1 | {{spar|return-type}} {{spar|class-name}}{{ttb|1=::operator}}{{spar|op}}{{ttb|(}} {{ttb|const}} {{spar|class-name}} {{ttb|&}} {{ttb|) const}} {{ttb|&}}{{mark optional}} {{ttb|1== default;}} }}
+
* It is a {{rlp|member functions|non-static member}} or {{rlp|friend}} of some class {{tt|C}}.
{{sdsc | num=2 | {{ttb|friend}} {{spar|return-type}} {{ttb|1=operator}}{{spar|op}}{{ttb|(}} {{ttb|const}} {{spar|class-name}} {{ttb|&}}{{ttb|,}} {{ttb|const}} {{spar|class-name}} {{ttb|&}} {{ttb|1=) = default;}}
+
* It is {{rlp|function#Function definition|defined as defaulted}} in {{tt|C}} or in a context where {{tt|C}} is {{rlp|type#Incomplete type|complete}}.
}}
+
* It has two parameters of type {{c/core|const C&}} or two parameters of type {{tt|C}}, where the {{rlp|overload resolution#Additional rules for member function candidates|implicit object parameter}} (if any) is considered to be the first parameter.
{{sdsc | num=3 | {{ttb|friend}} {{spar|return-type}} {{ttb|1=operator}}{{spar|op}}{{ttb|(}} {{spar|class-name}}{{ttb|,}} {{spar|class-name}} {{ttb|1=) = default;}}
+
}}
+
{{sdsc end}}
+
  
{{par begin}}
+
Such a comparison operator function is termed a ''defaulted comparison operator function for class {{tt|C}}''.
{{par | {{spar|op}} | a comparison operator ({{tt|1=<=>}}, {{tt|1===}}, {{tt|1=!=}}, {{tt|<}}, {{tt|>}}, {{tt|1=<=}}, or {{tt|1=>=}})  }}
+
{{par | {{spar|return-type}} | return type of the operator function. Must be
+
* {{rlpt|auto}} or one of [[#Custom comparisons and comparison categories|three comparison category types]] if {{spar|op}} is {{ttb|1=<=>}},
+
* otherwise, {{c|bool}}}}
+
{{par end}}
+
  
===Explanation===
+
{{source|1=
@1@ Declare the defaulted comparison function as a member function.
+
struct X
@2@ Declare the defaulted comparison function as a non-member function.
+
{
@3@ Declare the defaulted comparison function as a non-member function. Arguments are passed by value.
+
    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
 +
};
  
The three-way comparison function (whether defaulted or not) is called whenever values are compared using {{tt|<}}, {{tt|>}}, {{tt|1=<=}}, {{tt|1=>=}}, or {{tt|1=<=>}} and overload resolution selects this overload.
+
struct Y
 +
{
 +
    friend bool operator==(Y, Y) = default;        // OK
 +
    friend bool operator==(Y, const Y&) = default; // Error: different parameter types
 +
};
  
The equality comparison function (whether defaulted or not) is called whenever values are compared using {{tt|1===}} or {{tt|1=!=}} and overload resolution selects this overload.
+
bool operator==(const Y&, const Y&) = default;    // Error: not a friend of Y
 +
}}
  
Like defaulted special member functions, a defaulted comparison function is defined if {{rlp|definition#ODR-use|odr-used}} or {{rlp|constant expression#Functions and variables needed for constant evaluation|needed for constant evaluation}}.
+
Name lookups and access checks in the implicit definition of a comparison operator function are performed from a context equivalent to its function body. A definition of a comparison operator function as defaulted that appears in a class must be the first declaration of that function.
  
<!-- mention special overload resolution rule for comparison operators added in C++20? -->
+
===Default comparison order===
 +
Given a class {{tt|C}}, a subobject list is formed by the following subjects in order:
 +
* The direct base class subobjects of {{tt|C}}, in declaration order.
 +
* The non-static {{rlp|data members}} of {{tt|C}}, in declaration order.
 +
:* If any member subobject is of array type, it is expanded to the sequence of its elements, in the order of increasing subscript. The expansion is recursive: array elements of array types will be expanded again until there is no subobject of array type.
  
===Defaulted comparisons===
+
For any object {{c|x}} of type {{tt|C}}, in the following descriptions:
 +
* Let {{c|n}} be the number of subobjects in the (expanded) subobject list for {{c|x}}.
 +
* Let {{c|x_i}} be the {{c|i}}th subobject in the (expanded) subobject list for {{c|x}}, where {{c|x_i}} is formed by a sequence of {{rlp|overload resolution#Ranking of implicit conversion sequences|derived-to-base conversions}}, {{rlp|operator member access#Built-in member access operators|class member access expressions}}, and {{rlp|operator member access#Built-in subscript operator|array subscript expressions}} applied to {{c|x}}.
  
====Defaulted three-way comparison====
+
{{source|
The default {{c|1=operator<=>}} performs lexicographical comparison by successively comparing
+
struct S {};
the base (left-to-right depth-first) and then non-static member (in declaration order) subobjects of {{tt|T}} to compute {{tt|1=<=>}}, recursively expanding array members (in order of increasing subscript), and stopping early when a not-equal result is found, that is:
+
{{source|1=
+
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);
+
}}
+
  
It is unspecified whether virtual base subobjects are compared more than once.
+
struct T : S
 +
{
 +
    int arr[2][2];
 +
} t;
  
If the declared return type is {{c|auto}}, then the actual return type is the common comparison category of the base and member subobject and member array elements to be compared (see {{ltt|cpp/utility/compare/common_comparison_category|std::common_comparison_category}}). This makes it easier to write cases where the return type non-trivially depends on the members, such as:
+
// The subobject list for “t” consists of the following 5 subobjects in order:
{{source|1=
+
// (S)t → t[0][0] → t[0][1] → t[1][0] → t[1][1]
template<class T1, class T2>
+
struct P {
+
  T1 x1;
+
  T2 x2;
+
  friend auto operator<=>(const P&, const P&) = default;
+
};
+
 
}}
 
}}
  
Let {{c|R}} be the return type, each pair of subobjects {{c|a}}, {{c|b}} is compared as follows:
+
===Three-way comparison===
 +
An {{c/core|1=operator<=>}} for a class type can be defined as defaulted with any return type.
  
* If {{c|1=a <=> b}} is usable, the result of comparison is {{c|1=static_cast<R>(a <=> b)}}.
+
====Comparison category types====
* Otherwise, if overload resolution for {{c|1=operator<=>}} is performed for {{c|1=a <=> b}} and at least one candidate is found, the comparison is not defined ({{c|1=operator<=>}} is defined as deleted).
+
There are three comparison category types:
* Otherwise, if {{c|R}} is not a comparison category type (see below), or either {{c|1=a == b}} or {{c|a < b}} is not usable, the comparison is not defined ({{c|1=operator<=>}} is defined as deleted).
+
* {{ltt std|cpp/utility/compare/strong_ordering}}
* Otherwise, if {{c|R}} is {{c|std::strong_ordering}}, the result is
+
* {{ltt std|cpp/utility/compare/weak_ordering}}
 +
* {{ltt std|cpp/utility/compare/partial_ordering}}
 +
 
 +
{|class="wikitable" style="font-size:85%; text-align:center;"
 +
|-
 +
!Type
 +
!{{nbsp}}Equivalent values are..{{nbsp}}
 +
!{{nbsp}}Incomparable values are..{{nbsp}}
 +
|-
 +
|{{ltt std|cpp/utility/compare/strong_ordering}}
 +
|indistinguishable
 +
|not allowed
 +
|-
 +
|{{ltt std|cpp/utility/compare/weak_ordering}}
 +
|distinguishable
 +
|not allowed
 +
|-
 +
|{{nbsp}}{{ltt std|cpp/utility/compare/partial_ordering}}{{nbsp}}
 +
|distinguishable
 +
|allowed
 +
|}
 +
 
 +
====Synthesized three-way comparison====
 +
The ''synthesized three-way comparison'' of type {{tt|T}} between glvalues {{c|a}} and {{c|b}} of the same type is defined as follows:
 +
* If the overload resolution for {{c|1=a <=> b}} results in a usable candidate, and can be explicitly converted to {{tt|T}} using {{rlpt|static_cast}}, the synthesized comparison is {{c|1=static_cast<T>(a <=> b)}}.
 +
* Otherwise, if any of the following condition is satisfied, the synthesized comparison is not defined:
 +
:* The overload resolution for {{c|1=a <=> b}} finds at least one viable candidate.
 +
:* {{tt|T}} is not a comparison category type.
 +
:* The overload resolution for {{c|1=a == b}} does not result in a usable candidate.
 +
:* The overload resolution for {{c|a < b}} does not result in a usable candidate.
 +
* Otherwise, if {{tt|T}} is {{ltt std|cpp/utility/compare/strong_ordering}}, the synthesized comparison is
 
{{source|1=
 
{{source|1=
a == b ? R::equal :
+
a == b ? std::strong_ordering::equal :
a < b  ? R::less :
+
a < b  ? std::strong_ordering::less :
         R::greater
+
         std::strong_ordering::greater
 
}}
 
}}
* Otherwise, if {{c|R}} is {{c|std::weak_ordering}}, the result is
+
* Otherwise, if {{tt|T}} is {{ltt std|cpp/utility/compare/weak_ordering}}, the synthesized comparison is
 
{{source|1=
 
{{source|1=
a == b ? R::equivalent :
+
a == b ? std::weak_ordering::equivalent :
a < b  ? R::less :
+
a < b  ? std::weak_ordering::less :
         R::greater
+
         std::weak_ordering::greater
 
}}
 
}}
* Otherwise ({{c|R}} is {{c|std::partial_ordering}}), the result is
+
* Otherwise ({{tt|T}} is {{ltt std|cpp/utility/compare/partial_ordering}}), the synthesized comparison is
 
{{source|1=
 
{{source|1=
a == b ? R::equivalent :
+
a == b ? std::partial_ordering::equivalent :
a < b  ? R::less :
+
a < b  ? std::partial_ordering::less :
b < a  ? R::greater :  
+
b < a  ? std::partial_ordering::greater :  
         R::unordered
+
         std::partial_ordering::unordered
 
}}
 
}}
  
Per the rules for any {{c|1=operator<=>}} overload, a defaulted {{tt|1=<=>}} overload will also allow the type to be compared with {{tt|1=<}}, {{tt|1=<=}}, {{tt|1= >}}, and {{tt|1= >=}}.
+
====Placeholder return type====
 +
If the declared return type of a defaulted three-way comparison operator function ({{c/core|1=operator<=>}}) for a class type {{tt|C}} is {{c/core|auto}}, the return type is deduced from the return types of the three-way comparisons between the corresponding subobjects of an object {{c|x}} of type {{tt|C}}.
  
If {{c|1=operator<=>}} is defaulted and {{c|1=operator==}} is not declared at all, then {{c|1=operator==}} is implicitly defaulted.
+
For each subobject {{c|x_i}} in the [[#Default comparison order|(expanded) subobject list]] for {{c|x}}:
 +
# Perform overload resolution for {{c|1=x_i <=> x_i}}, if the overload resolution does not result in a usable candidate, the defaulted {{c/core|1=operator<=>}} is defined as deleted.
 +
# Denote the cv-unqualified version of the type of {{c|1=x_i <=> x_i}} as {{tt|R_i}}, if {{tt|R_i}} is not a comparison category type, the defaulted {{c/core|1=operator<=>}} is defined as deleted.
  
{{example|code=
+
If the defaulted {{c/core|1=operator<=>}} is not defined as deleted, its return type is deduced as {{c/core|std::common_comparison_category_t<R_1, R_2, ..., R_n>}}.
#include <compare>
+
struct Point {
+
  int x;
+
  int y;
+
  auto operator<=>(const Point&) const = default;
+
  // ... non-comparison functions ...
+
};
+
// compiler generates all six two-way comparison operators
+
  
#include <iostream>
+
====Non-placeholder return type====
#include <set>
+
If the declared return type of the defaulted {{c/core|1=operator<=>}} is not {{c/core|auto}}, it cannot contain any {{rlp|auto|placeholder type}} (e.g. {{c/core|decltype(auto)}}).
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
+
}
+
}}
+
  
====Defaulted equality comparison====
+
If there is a subobject {{c|x_i}} in the (expanded) subobject list for {{c|x}} such that the [[#Synthesized three-way comparison|synthesized three-way comparison]] of the declared return type between {{c|x_i}} and {{c|x_i}} is not defined, the defaulted {{c/core|1=operator<=>}} is defined as deleted.
A class can define {{c|1=operator==}} as defaulted, with a return value of {{c|bool}}. This will generate an equality comparison of each base class and member subobject, in their declaration order. Two objects are equal if the values of their base classes and members are equal. The test will short-circuit if an inequality is found in members or base classes earlier in declaration order.
+
  
Per the rules for {{c|1=operator==}}, this will also allow inequality testing:
+
====Comparison result====
 +
Let {{c|x}} and {{c|y}} be the parameters of a defaulted {{c/core|1=operator<=>}}, denote each subobject in the (expanded) subobject list for {{c|x}} and {{c|y}} as {{c|x_i}} and {{c|y_i}} respectively. The default three-way comparison between {{c|x}} and {{c|y}} is performed by comparing corresponding subobjects {{c|x_i}} and {{c|y_i}} with increasing {{c|i}} order.
  
{{example|code=
+
Let {{tt|R}} be the (possibly-deduced) return type, the comparison result between {{c|x_i}} and {{c|y_i}} is the result of the synthesized three-way comparison of type {{tt|R}} between {{c|x_i}} and {{c|y_i}}.
struct Point {
+
* During the default three-way comparison between {{c|x}} and {{c|y}}, if a subobject-wise comparison between {{c|x_i}} and {{c|y_i}} generates a result {{c|v_i}} such that contextually converting {{c|1=v_i != 0}} to {{c/core|bool}} yields {{c|true}}, the return value is a copy of {{c|v_i}} (the remaining subobjects will not be compared).
  int x;
+
* Otherwise, the return value is {{c|static_cast<R>(std::strong_ordering::equal)}}.
  int y;
+
  bool operator==(const Point&) const = default;
+
  // ... non-comparison functions ...
+
};
+
// compiler generates element-wise equality testing
+
  
 +
{{example
 +
|code=
 +
#include <compare>
 
#include <iostream>
 
#include <iostream>
int main() {
+
#include <set>
  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
+
}
+
}}
+
  
====Other defaulted comparison operators====
+
struct Point
Any of the four relational operators can be explicitly defaulted. A defaulted relational operator must have the return type {{c|bool}}.
+
{
 +
    int x;
 +
    int y;
 +
    auto operator<=>(const Point&) const = default;
 +
    /* non-comparison functions */
 +
};
  
Such operator will be deleted if overload resolution over {{c|1=x <=> y}} (considering also {{c|1=operator<=>}} with reversed order of parameters) fails, or if this {{c|1=operator@}} is not applicable to the result of that {{c|1=x <=> y}}. Otherwise, the defaulted {{c|1=operator@}} calls {{c|1=x <=> y @ 0}} if an {{c|1=operator<=>}} with the original order of parameters was selected by overload resolution, or {{c|1=0 @ y <=> x}} otherwise:
+
int main()
{{source|1=
+
{
struct HasNoRelational {};
+
    Point pt1{1, 1}, pt2{1, 2};
 
+
    std::set<Point> s; // OK
struct C {
+
    s.insert(pt1);    // OK
  friend HasNoRelational operator<=>(const C&, const C&);
+
   
  bool operator<(const C&) = default;                       // ok, function is deleted
+
    // 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
 +
}
 
}}
 
}}
  
Similarly, {{c|1=operator!=}} can be defaulted. It is deleted if overload resolution over {{c|1=x == y}} (considering also {{c|1=operator==}} with reversed order of parameters) fails, or if the result of {{c|1=x == y}} does not have type {{c|bool}}. The defaulted {{c|1=operator!=}} calls {{c|1=!(x == y)}} or {{c|1=!(y == x)}} as selected by overload resolution.
+
===Equality comparison===
 +
====Explicit declaration====
 +
An {{c/core|1=operator==}} for a class type can be defined as defaulted with return type {{c/core|bool}}.
  
Defaulting the relational operators can be useful in order to create functions whose addresses may be taken. For other uses, it is sufficient to provide only {{c|1=operator<=>}} and {{c|1=operator==}}.
+
Given a class {{tt|C}} and an object {{c|x}} of type {{tt|C}}, if there is a subobject {{c|x_i}} in the (expanded) subobject list for {{c|x}} such that the overload resolution for {{c|1=x_i == x_i}} does not result in a usable candidate, the defaulted {{c/core|1=operator==}} is defined as deleted.
  
===Custom comparisons and comparison categories===
+
Let {{c|x}} and {{c|y}} be the parameters of a defaulted {{c/core|1=operator==}}, denote each subobject in the (expanded) subobject list for {{c|x}} and {{c|y}} as {{c|x_i}} and {{c|y_i}} respectively. The default equality comparison between {{c|x}} and {{c|y}} is performed by comparing corresponding subobjects {{c|x_i}} and {{c|y_i}} with increasing {{c|i}} order.
When the default semantics are not suitable, such as when the members must be compared out of order, or must use a comparison that's different from their natural comparison, then the programmer can write {{c|1=operator<=>}} and let the compiler generate the appropriate two-way comparison operators. The kind of two-way comparison operators generated depends on the return type of the user-defined {{c|1=operator<=>}}.
+
  
There are three available return types:
+
The comparison result between {{c|x_i}} and {{c|y_i}} is the result of {{c|1=x_i == y_i}}.
{| class="wikitable" style="font-size:85%; text-align:center;"
+
* During the default equality comparison between {{c|x}} and {{c|y}}, if a subobject-wise comparison between {{c|x_i}} and {{c|y_i}} generates a result {{c|v_i}} such that contextually converting {{c|v_i}} to {{c/core|bool}} yields {{c|false}}, the return value is {{c|false}} (the remaining subobjects will not be compared).
|-
+
* Otherwise, the return value is {{c|true}}.
! Return type
+
! Equivalent values are..
+
! Incomparable values are..
+
|-
+
| {{ltt|cpp/utility/compare/strong_ordering|std::strong_ordering}}
+
| indistinguishable
+
| not allowed
+
|-
+
| {{ltt|cpp/utility/compare/weak_ordering|std::weak_ordering}}
+
| distinguishable
+
| not allowed
+
|-
+
| {{ltt|cpp/utility/compare/partial_ordering|std::partial_ordering}}
+
| distinguishable
+
| allowed
+
|}
+
  
====Strong ordering====
+
{{example
An example of a custom {{c|1=operator<=>}} that returns {{ltt|cpp/utility/compare/strong_ordering|std::strong_ordering}} is an operator that compares every member of a class, except in order that is different from the default (here: last name first).
+
|code=
 +
#include <iostream>
  
{{example|code=
+
struct Point
#include <compare>
+
{
#include <string>
+
     int x;
struct Base {
+
     int y;
     std::string zip;
+
    bool operator==(const Point&) const = default;
     auto operator<=>(const Base&) const = default;
+
    /* non-comparison functions */
 
};
 
};
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
 
  
#include <cassert>
+
int main()
#include <set>
+
{
int main() {
+
    Point pt1{3, 5}, pt2{2, 5};
  TotallyOrdered to1{"a","b","c","d"}, to2{"a","b","d","c"};
+
    std::cout << std::boolalpha
  std::set<TotallyOrdered> s; // ok
+
        << (pt1 != pt2) << '\n'  // true
  s.insert(to1); // ok
+
        << (pt1 == pt1) << '\n'; // true
  assert(to2 <= to1); // ok, single call to <=>
+
   
 +
    struct [[maybe_unused]] { int x{}, y{}; } p, q;
 +
    // if (p == q) {} // Error: operator== is not defined
 
}
 
}
 
}}
 
}}
  
Note: an operator that returns a {{ltt|cpp/utility/compare/strong_ordering|std::strong_ordering}} should compare every member, because if any member is left out, substitutability can be compromised: it becomes possible to distinguish two values that compare equal.
+
====Implicit declaration====
 
+
If a class {{tt|C}} does not explicitly declare any member or friend named {{c/core|1=operator==}}, an {{tt|==}} operator function is declared implicitly for each {{c/core|1=operator<=>}} defined as defaulted. Each implicity-declared {{c/core|1=operator==}} have the same access and {{rlpsd|function#Function definition}} and in the same {{rlpsd|scope#Class scope}} as the respective defaulted {{c/core|1=operator<=>}}, with the following changes:
====Weak ordering====
+
* The {{rlp|declarations#Declarators|declarator identifier}} is replaced with {{c/core|1=operator==}}.
An example of a custom {{c|1=operator<=>}} that returns {{ltt|cpp/utility/compare/weak_ordering|std::weak_ordering}} is an operator that compares string members of a class in case-insensitive manner: this is different from the default comparison (so a custom operator is required) and it's possible to distinguish two strings that compare equal under this comparison.
+
* The return type is replaced with {{c/core|bool}}.
  
 
{{source|1=
 
{{source|1=
class CaseInsensitiveString {
+
template<typename T>
  std::string s;
+
struct X
public:
+
{
  std::weak_ordering operator<=>(const CaseInsensitiveString& b) const {
+
    friend constexpr std::partial_ordering operator<=>(X, X)
    return case_insensitive_compare(s.c_str(), b.s.c_str());
+
        requires (sizeof(T) != 1) = default;
  }
+
    // implicitly declares: friend constexpr bool operator==(X, X)
  std::weak_ordering operator<=>(const char* b) const {
+
    //                          requires (sizeof(T) != 1) = default;
     return case_insensitive_compare(s.c_str(), b);
+
   
  }
+
    [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default;
  // ... non-comparison functions ...
+
     // implicitly declares: [[nodiscard]] virtual bool
 +
    //                         operator==(const X&) const = default;
 
};
 
};
 +
}}
  
// Compiler generates all four relational operators
+
===Secondary comparison===
CaseInsensitiveString cis1, cis2;
+
A secondary comparison operator function ({{tt|1=!=}}, {{tt|<}}, {{tt|>}}, {{tt|1=<=}}, or {{tt|1=>=}}) for a class type can be defined as defaulted with return type {{c/core|bool}}.
std::set<CaseInsensitiveString> s; // ok
+
s.insert(/*...*/); // ok
+
if (cis1 <= cis2) { /*...*/ } // ok, performs one comparison operation
+
  
// Compiler also generates all eight heterogeneous relational operators
+
Let {{tt|@}} be one of the five secondary comparison operators, for each defaulted {{c/core|operator@}} with parameters {{c|x}} and {{c|y}}, up to two overloads resolutions are performed (not considering the defaulted {{c/core|operator@}} as a candidate) to determine whether it is defined as deleted.
if (cis1 <= "xyzzy") { /*...*/ } // ok, performs one comparison operation
+
* The first overload resolution is performed for {{c|x @ y}}. If the overload resolution does not result in a usable candidate, or the selected candidate is not a {{rlp|overload resolution#Call to an overloaded operator|rewritten candidate}}, the defaulted {{c/core|operator@}} is defined as deleted. There is no second overload resolution in these cases.
if ("xyzzy" >= cis1) { /*...*/ } // ok, identical semantics
+
* The second overload resolution is performed for the selected rewritten candidate of {{c|x @ y}}. If the overload resolution does not result in a usable candidate, the defaulted {{c/core|operator@}} is defined as deleted.
}}
+
  
Note that this example demonstrates the effect a heterogeneous {{c|1=operator<=>}} has: it generates heterogeneous comparisons in both directions.
+
If is {{c|x @ y}} cannot be implicitly converted to {{c/core|bool}}, the defaulted {{c/core|operator@}} is defined as deleted.
 +
 
 +
If the defaulted {{c/core|operator@}} is not defined as deleted, it yields {{c|x @ y}}.
  
====Partial ordering====
 
Partial ordering is an ordering that allows incomparable (unordered) values, such as NaN values in floating-point ordering, or, in this example, persons that are not related:
 
 
{{source|1=
 
{{source|1=
class PersonInFamilyTree { // ...
+
struct HasNoRelational {};
public:
+
 
  std::partial_ordering operator<=>(const PersonInFamilyTree& that) const {
+
struct C
    if (this->is_the_same_person_as ( that)) return partial_ordering::equivalent;
+
{
     if (this->is_transitive_child_of( that)) return partial_ordering::less;
+
    friend HasNoRelational operator<=>(const C&, const C&);
    if (that. is_transitive_child_of(*this)) return partial_ordering::greater;
+
     bool operator<(const C&) const = default; // OK, function is defaulted
    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
 
 
}}
 
}}
 +
 +
===Keywords===
 +
{{ltt|cpp/keyword/default}}
 +
 +
===Defect reports===
 +
{{dr list begin}}
 +
{{dr list item|wg=cwg|dr=2539|std=C++20|before=the synthesized three-way comparison would choose<br>{{c/core|static_cast}} even if the explicit conversion is not available|after=does not choose<br>{{c/core|static_cast}} in this case}}
 +
{{dr list item|wg=cwg|dr=2546|std=C++20|before=the defaulted secondary {{c/core|operator@}} was not<br>defined as deleted if the overload resolution of<br>{{c|x @ y}} selects a non-usable rewritten candidate|after=defined as deleted<br>in this case}}
 +
{{dr list item|wg=cwg|dr=2547|std=C++20|before=it was unclear whether comparison operator<br>functions for non-classes can be defaulted|after=they cannot be defaulted}}
 +
{{dr list item|wg=cwg|dr=2568|std=C++20|before=the implicit definition of comparison operator<br>functions might violate member access rules|after=access checks are performed<br>from a context equivalent<br>to their function bodies}}
 +
{{dr list end}}
  
 
===See also===
 
===See also===
* {{rlp|overload_resolution#Call_to_an_overloaded_operator|overload resolution}} in a call to an overloaded operator
+
* {{rlp|overload resolution#Call to an overloaded operator|overload resolution}} in a call to an overloaded operator
* Built-in {{rlp|operator_comparison#Three-way_comparison|three-way comparison operator}}
+
* Built-in {{rlp|operator comparison#Three-way comparison|three-way comparison operator}}
* {{rlp|operators#Comparison_operators|Operator overloading}} for comparison operators
+
* {{rlp|operators#Comparison operators|Operator overloading}} for comparison operators
  
 
{{langlinks|es|ja|ru|zh}}
 
{{langlinks|es|ja|ru|zh}}

Latest revision as of 17:33, 18 August 2024

 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 

Comparison operator functions can be explicitly defaulted to request the compiler to generate the corresponding default comparison for a class.

Contents

[edit] Definition

A defaulted comparison operator function is a non-template comparison operator function (i.e. <=>, ==, !=, <, >, <=, or >=) satisfying all following conditions:

Such a comparison operator function is termed a defaulted comparison operator function for class 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

Name lookups and access checks in the implicit definition of a comparison operator function are performed from a context equivalent to its function body. A definition of a comparison operator function as defaulted that appears in a class must be the first declaration of that function.

[edit] Default comparison order

Given a class C, a subobject list is formed by the following subjects in order:

  • The direct base class subobjects of C, in declaration order.
  • The non-static data members of C, in declaration order.
  • If any member subobject is of array type, it is expanded to the sequence of its elements, in the order of increasing subscript. The expansion is recursive: array elements of array types will be expanded again until there is no subobject of array type.

For any object x of type C, in the following descriptions:

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]

[edit] Three-way comparison

An operator<=> for a class type can be defined as defaulted with any return type.

[edit] Comparison category types

There are three comparison category types:

Type  Equivalent values are..   Incomparable values are.. 
std::strong_ordering indistinguishable not allowed
std::weak_ordering distinguishable not allowed
 std::partial_ordering  distinguishable allowed

[edit] Synthesized three-way comparison

The synthesized three-way comparison of type T between glvalues a and b of the same type is defined as follows:

  • If the overload resolution for a <=> b results in a usable candidate, and can be explicitly converted to T using static_cast, the synthesized comparison is static_cast<T>(a <=> b).
  • Otherwise, if any of the following condition is satisfied, the synthesized comparison is not defined:
  • The overload resolution for a <=> b finds at least one viable candidate.
  • T is not a comparison category type.
  • The overload resolution for a == b does not result in a usable candidate.
  • The overload resolution for a < b does not result in a usable candidate.
a == b ? std::strong_ordering::equal :
a < b  ? std::strong_ordering::less :
         std::strong_ordering::greater
a == b ? std::weak_ordering::equivalent :
a < b  ? std::weak_ordering::less :
         std::weak_ordering::greater
a == b ? std::partial_ordering::equivalent :
a < b  ? std::partial_ordering::less :
b < a  ? std::partial_ordering::greater : 
         std::partial_ordering::unordered

[edit] Placeholder return type

If the declared return type of a defaulted three-way comparison operator function (operator<=>) for a class type C is auto, the return type is deduced from the return types of the three-way comparisons between the corresponding subobjects of an object x of type C.

For each subobject x_i in the (expanded) subobject list for x:

  1. Perform overload resolution for x_i <=> x_i, if the overload resolution does not result in a usable candidate, the defaulted operator<=> is defined as deleted.
  2. Denote the cv-unqualified version of the type of x_i <=> x_i as R_i, if R_i is not a comparison category type, the defaulted operator<=> is defined as deleted.

If the defaulted operator<=> is not defined as deleted, its return type is deduced as std::common_comparison_category_t<R_1, R_2, ..., R_n>.

[edit] Non-placeholder return type

If the declared return type of the defaulted operator<=> is not auto, it cannot contain any placeholder type (e.g. decltype(auto)).

If there is a subobject x_i in the (expanded) subobject list for x such that the synthesized three-way comparison of the declared return type between x_i and x_i is not defined, the defaulted operator<=> is defined as deleted.

[edit] Comparison result

Let x and y be the parameters of a defaulted operator<=>, denote each subobject in the (expanded) subobject list for x and y as x_i and y_i respectively. The default three-way comparison between x and y is performed by comparing corresponding subobjects x_i and y_i with increasing i order.

Let R be the (possibly-deduced) return type, the comparison result between x_i and y_i is the result of the synthesized three-way comparison of type R between x_i and y_i.

  • During the default three-way comparison between x and y, if a subobject-wise comparison between x_i and y_i generates a result v_i such that contextually converting v_i != 0 to bool yields true, the return value is a copy of v_i (the remaining subobjects will not be compared).
  • Otherwise, the return value is 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
}

[edit] Equality comparison

[edit] Explicit declaration

An operator== for a class type can be defined as defaulted with return type bool.

Given a class C and an object x of type C, if there is a subobject x_i in the (expanded) subobject list for x such that the overload resolution for x_i == x_i does not result in a usable candidate, the defaulted operator== is defined as deleted.

Let x and y be the parameters of a defaulted operator==, denote each subobject in the (expanded) subobject list for x and y as x_i and y_i respectively. The default equality comparison between x and y is performed by comparing corresponding subobjects x_i and y_i with increasing i order.

The comparison result between x_i and y_i is the result of x_i == y_i.

  • During the default equality comparison between x and y, if a subobject-wise comparison between x_i and y_i generates a result v_i such that contextually converting v_i to bool yields false, the return value is false (the remaining subobjects will not be compared).
  • Otherwise, the return value is 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
}

[edit] Implicit declaration

If a class C does not explicitly declare any member or friend named operator==, an operator function is declared implicitly for each operator<=> defined as defaulted. Each implicity-declared operator== have the same access and function definition and in the same class scope as the respective defaulted operator<=>, with the following changes:

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;
};

[edit] Secondary comparison

A secondary comparison operator function (!=, <, >, <=, or >=) for a class type can be defined as defaulted with return type bool.

Let @ be one of the five secondary comparison operators, for each defaulted operator@ with parameters x and y, up to two overloads resolutions are performed (not considering the defaulted operator@ as a candidate) to determine whether it is defined as deleted.

  • The first overload resolution is performed for x @ y. If the overload resolution does not result in a usable candidate, or the selected candidate is not a rewritten candidate, the defaulted operator@ is defined as deleted. There is no second overload resolution in these cases.
  • The second overload resolution is performed for the selected rewritten candidate of x @ y. If the overload resolution does not result in a usable candidate, the defaulted operator@ is defined as deleted.

If is x @ y cannot be implicitly converted to bool, the defaulted operator@ is defined as deleted.

If the defaulted operator@ is not defined as deleted, it yields x @ y.

struct HasNoRelational {};
 
struct C
{
    friend HasNoRelational operator<=>(const C&, const C&);
    bool operator<(const C&) const = default; // OK, function is defaulted
};

[edit] Keywords

default

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 2539 C++20 the synthesized three-way comparison would choose
static_cast even if the explicit conversion is not available
does not choose
static_cast in this case
CWG 2546 C++20 the defaulted secondary operator@ was not
defined as deleted if the overload resolution of
x @ y selects a non-usable rewritten candidate
defined as deleted
in this case
CWG 2547 C++20 it was unclear whether comparison operator
functions for non-classes can be defaulted
they cannot be defaulted
CWG 2568 C++20 the implicit definition of comparison operator
functions might violate member access rules
access checks are performed
from a context equivalent
to their function bodies

[edit] See also