Namespaces
Variants
Views
Actions

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

From cppreference.com
< cpp‎ | language
m (Minor fix.)
 
(34 intermediate revisions by 14 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 relational operators for a class.
+
Comparison operator functions can be explicitly defaulted to request the compiler to generate the corresponding default comparison for a class.
  
===Defaulted three-way comparison===
+
===Definition===
The default operator<=> performs lexicographical comparison by successively comparing
+
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:
the base (left-to-right depth-first) and then non-static member (in declaration order) subobjects of T to compute <=>, recursively expanding array members (in order of increasing subscript), and stopping early when a not-equal result is found, that is:
+
* It is a {{rlp|member functions|non-static member}} or {{rlp|friend}} of some class {{tt|C}}.
{{source|1=
+
* 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}}.
for /*each base or member subobject o of T*/
+
* 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.
  if (auto cmp = lhs.o <=> rhs.o; cmp != 0) return cmp;
+
return strong_ordering::equal; // converts to everything
+
}}
+
  
It is unspecified whether virtual base subobjects are compared more than once.
+
Such a comparison operator function is termed a ''defaulted comparison operator function for class {{tt|C}}''.
  
If the declared return type is {{c|auto}}, then the actual return type is {{ltt|cpp/utility/compare/common_comparison_category|std::common_comparison_category_t<Ms>}} where Ms is the list (possibly empty) of the types of base and member subobject and member array elements to be compared. This makes it easier to write cases where the return type non-trivially depends on the members, such as:
 
 
{{source|1=
 
{{source|1=
template<class T1, class T2>
+
struct X
struct P {
+
{
T1 x1;
+
    bool operator==(const X&) const = default; // OK
T2 x2;
+
    bool operator==(const X&) = default;      // Error: the implicit object
friend auto operator<=>(const P&, const P&) = default;
+
                                              //        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
 
}}
 
}}
  
Otherwise, the return type must be one of the three comparison types (see above), and is ill-formed if the expression m1 <=> m2 for any base or member subobject or member array element is not implicitly convertible to the chosen return type.
+
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.
  
The defaulted operator<=> is implicitly deleted and returns {{c|void}} if not all base and member subobjects have a compiler-generated or user-declared operator<=> declared in their scope (i.e., as a nonstatic member or as a friend) whose result is one of the std:: comparison category types
+
===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.
  
Per the rules for any {{c|1=operator<=>}} overload, a defaulted <=> overload will also allow the type to be compared with <, <=, >, and >=.
+
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}}.
  
If {{c|1=operator<=>}} is defaulted and {{c|1=operator==}} is not declared at all, then {{c|1=operator==}} is implicitly defaulted.
+
{{source|
 +
struct S {};
  
{{example|code=
+
struct T : S
#include <compare>
+
{
struct Point {
+
    int arr[2][2];
  int x;
+
} t;
  int y;
+
  auto operator<=>(const Point&) const = default;
+
  // ... non-comparison functions ...
+
};
+
// compiler generates all four relational operators
+
  
#include <iostream>
+
// The subobject list for “t” consists of the following 5 subobjects in order:
#include <set>
+
// (S)t → t[0][0] → t[0][1] → t[1][0] → t[1][1]
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===
+
===Three-way comparison===
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.
+
An {{c/core|1=operator<=>}} for a class type can be defined as defaulted with any return type.
  
Per the rules for {{c|1=operator==}}, this will also allow inequality testing:
+
====Comparison category types====
 +
There are three comparison category types:
 +
* {{ltt std|cpp/utility/compare/strong_ordering}}
 +
* {{ltt std|cpp/utility/compare/weak_ordering}}
 +
* {{ltt std|cpp/utility/compare/partial_ordering}}
  
{{example|code=
+
{|class="wikitable" style="font-size:85%; text-align:center;"
struct Point {
+
|-
  int x;
+
!Type
  int y;
+
!{{nbsp}}Equivalent values are..{{nbsp}}
  bool operator==(const Point&) const = default;
+
!{{nbsp}}Incomparable values are..{{nbsp}}
  // ... non-comparison functions ...
+
|-
};
+
|{{ltt std|cpp/utility/compare/strong_ordering}}
// compiler generates element-wise equality testing
+
|indistinguishable
 +
|not allowed
 +
|-
 +
|{{ltt std|cpp/utility/compare/weak_ordering}}
 +
|distinguishable
 +
|not allowed
 +
|-
 +
|{{nbsp}}{{ltt std|cpp/utility/compare/partial_ordering}}{{nbsp}}
 +
|distinguishable
 +
|allowed
 +
|}
  
#include <iostream>
+
====Synthesized three-way comparison====
int main() {
+
The ''synthesized three-way comparison'' of type {{tt|T}} between glvalues {{c|a}} and {{c|b}} of the same type is defined as follows:
  Point pt1{3, 5}, pt2{2, 5};
+
* 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)}}.
  std::cout << std::boolalpha
+
* Otherwise, if any of the following condition is satisfied, the synthesized comparison is not defined:
    << (pt1 != pt2) << '\n'  // true
+
:* The overload resolution for {{c|1=a <=> b}} finds at least one viable candidate.
    << (pt1 == pt1) << '\n'; // true
+
:* {{tt|T}} is not a comparison category type.
  struct [[maybe_unused]] { int x{}, y{}; } p, q;
+
:* The overload resolution for {{c|1=a == b}} does not result in a usable candidate.
  // if (p == q) { } // Error: 'operator==' is not defined
+
:* 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=
 +
a == b ? std::strong_ordering::equal :
 +
a < b  ? std::strong_ordering::less :
 +
        std::strong_ordering::greater
 +
}}
 +
* Otherwise, if {{tt|T}} is {{ltt std|cpp/utility/compare/weak_ordering}}, the synthesized comparison is
 +
{{source|1=
 +
a == b ? std::weak_ordering::equivalent :
 +
a < b  ? std::weak_ordering::less :
 +
        std::weak_ordering::greater
 +
}}
 +
* Otherwise ({{tt|T}} is {{ltt std|cpp/utility/compare/partial_ordering}}), the synthesized comparison is
 +
{{source|1=
 +
a == b ? std::partial_ordering::equivalent :
 +
a < b  ? std::partial_ordering::less :
 +
b < a  ? std::partial_ordering::greater :
 +
        std::partial_ordering::unordered
 
}}
 
}}
  
===Other defaulted comparison operators===
+
====Placeholder return type====
Any of the four relational operators can be explicitly defaulted. A defaulted relational operator must have the return type {{c|bool}}.
+
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}}.
  
Such operator will be deleted if overload resolution over x <=> y (considering also operator<=> with reversed order of parameters) fails, or if this operator@ is not applicable to the result of that x<=>y. Otherwise, the defaulted operator@ calls {{c|1=x <=> y @ 0}} if an operator<=> with the original order of parameters was selected by overload resolution, or {{c|1=0 @ y <=> x}} otherwise:
+
For each subobject {{c|x_i}} in the [[#Default comparison order|(expanded) subobject list]] for {{c|x}}:
{{source|1=
+
# 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.
struct HasNoRelational {};
+
# 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.
  
struct C {
+
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>}}.
  friend HasNoRelational operator<=>(const C&, const C&);
+
 
  bool operator<(const C&) = default;                       // ok, function is deleted
+
====Non-placeholder return type====
 +
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)}}).
 +
 
 +
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.
 +
 
 +
====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.
 +
 
 +
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}}.
 +
* 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).
 +
* Otherwise, the return value is {{c|static_cast<R>(std::strong_ordering::equal)}}.
 +
 
 +
{{example
 +
|code=
 +
#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
 +
}
 
}}
 
}}
  
Similarly, operator!= can be defaulted. It is deleted if overload resolution over x == y (considering also operator== with reversed order of parameters) fails, or if the result does not have type {{c|bool}}. The defaulted 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==
+
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 relational operators. The kind of relational 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
+
! Operators
+
! 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 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>
  
{{source|1=
+
struct Point
class TotallyOrdered : Base {
+
{
  std::string tax_id;
+
    int x;
  std::string first_name;
+
    int y;
  std::string last_name;
+
    bool operator==(const Point&) const = default;
public:
+
    /* non-comparison functions */
// 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
+
 
TotallyOrdered to1, to2;
+
int main()
std::set<TotallyOrdered> s; // ok
+
{
s.insert(to1); // ok
+
    Point pt1{3, 5}, pt2{2, 5};
if (to1 <= to2) { /*...*/ } // ok, single call to <=>
+
    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
 +
}
 
}}
 
}}
  
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 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 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
 
 
}}
 
}}
  
==See also==
+
===Keywords===
* {{rlp|overload_resolution#Call_to_an_overloaded_operator|overload resolution}} in a call to an overloaded operator
+
{{ltt|cpp/keyword/default}}
* Built-in {{rlp|operator_comparison#Three-way_comparison|three-way comparison operator}}
+
 
* {{rlp|operators#Relational_operators|Operator overloading}} for relational operators
+
===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===
 +
* {{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}}
 +
* {{rlp|operators#Comparison operators|Operator overloading}} for comparison operators
  
{{langlinks|ja|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