Namespaces
Variants
Views
Actions

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

From cppreference.com
< cpp‎ | language
(Move down the description of defaulted operator==)
m (Minor fix.)
 
(37 intermediate revisions by 16 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.
  
A class that defines {{c|1=operator<=>}} as defaulted will generate a 3-way comparison element-wise. It will perform 3-way comparisons on each base class and member subobject, in declaration order. These comparisons will short-circuit on the first non-equal base class or member. If the return type of the defaulted function is {{c|auto}}, then the comparison type will be the most restrictive of all of the 3-way comparison results for its subobjects.
+
===Definition===
 +
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:
 +
* It is a {{rlp|member functions|non-static member}} or {{rlp|friend}} of some class {{tt|C}}.
 +
* 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.
  
Per the rules for any {{c|1=operator<=>}} overload, a defaulted <=> overload will also allow the type to be compared with <, <=, >, and >=. If {{c|1=operator<=>}} is defaulted and {{c|1=operator==}} is not declared at all, then {{c|1=operator==}} is implicitly defaulted.
+
Such a comparison operator function is termed a ''defaulted comparison operator function for class {{tt|C}}''.
  
{{example|code=
+
{{source|1=
#include <compare>
+
struct X
struct Point {
+
{
  int x;
+
    bool operator==(const X&) const = default; // OK
  int y;
+
    bool operator==(const X&) = default;       // Error: the implicit object
  auto operator<=>(const Point&) const = default;
+
                                              //        parameter type is X&
  // ... non-comparison functions ...
+
    bool operator==(this X, X) = default;      // OK
 
};
 
};
// compiler generates all four relational operators
 
  
#include <iostream>
+
struct Y
#include <set>
+
{
int main() {
+
    friend bool operator==(Y, Y) = default;       // OK
  Point pt1{1, 1}, pt2{1, 2};
+
     friend bool operator==(Y, const Y&) = default; // Error: different parameter types
  std::set<Point> s; // ok
+
};
  s.insert(pt1);     // ok
+
 
  std::cout << std::boolalpha
+
bool operator==(const Y&, const Y&) = default;     // Error: not a friend of Y
     << (pt1 == pt2) << ' ' // false; operator== is implicitly defaulted.
+
    << (pt1 != pt2) << ' ' // true
+
    << (pt1 <  pt2) << ' ' // true
+
    << (pt1 <= pt2) << ' ' // true
+
    << (pt1 >  pt2) << ' ' // false
+
    << (pt1 >= pt2) << ' ';// false
+
}
+
 
}}
 
}}
  
==Custom comparisons==
+
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.
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:
+
===Default comparison order===
{| class="wikitable" style="font-size:85%; text-align:center;"
+
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.
 +
 
 +
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}}.
 +
 
 +
{{source|
 +
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]
 +
}}
 +
 
 +
===Three-way comparison===
 +
An {{c/core|1=operator<=>}} for a class type can be defined as defaulted with any return type.
 +
 
 +
====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}}
 +
 
 +
{|class="wikitable" style="font-size:85%; text-align:center;"
 
|-
 
|-
! Return type
+
!Type
! Operators
+
!{{nbsp}}Equivalent values are..{{nbsp}}
! Equivalent values are..
+
!{{nbsp}}Incomparable values are..{{nbsp}}
! Incomparable values are..
+
 
|-
 
|-
| {{ltt|cpp/utility/compare/strong_ordering|std::strong_ordering}}
+
|{{ltt std|cpp/utility/compare/strong_ordering}}
| == != < > <= >=
+
|indistinguishable
| indistinguishable
+
|not allowed
| not allowed
+
 
|-
 
|-
| {{ltt|cpp/utility/compare/weak_ordering|std::weak_ordering}}
+
|{{ltt std|cpp/utility/compare/weak_ordering}}
| == != < > <= >=
+
|distinguishable
| distinguishable
+
|not allowed
| not allowed
+
 
|-
 
|-
| {{ltt|cpp/utility/compare/partial_ordering|std::partial_ordering}}
+
|{{nbsp}}{{ltt std|cpp/utility/compare/partial_ordering}}{{nbsp}}
| == != < > <= >=
+
|distinguishable
| distinguishable
+
|allowed
| allowed
+
 
|}
 
|}
  
===Strong ordering===
+
====Synthesized three-way comparison====
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)
+
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=
class TotallyOrdered : Base {
+
a == b ? std::strong_ordering::equal :
  std::string tax_id;
+
a < b  ? std::strong_ordering::less :
  std::string first_name;
+
        std::strong_ordering::greater
  std::string last_name;
+
}}
public:
+
* Otherwise, if {{tt|T}} is {{ltt std|cpp/utility/compare/weak_ordering}}, the synthesized comparison is
// custom operator<=> because we want to compare last names first:
+
{{source|1=
std::strong_ordering operator<=>(const TotallyOrdered& that) const {
+
a == b ? std::weak_ordering::equivalent :
  if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0)
+
a < b ? std::weak_ordering::less :
      return cmp;
+
        std::weak_ordering::greater
  if (auto cmp = last_name <=> that.last_name; cmp != 0)
+
}}
      return cmp;
+
* Otherwise ({{tt|T}} is {{ltt std|cpp/utility/compare/partial_ordering}}), the synthesized comparison is
  if (auto cmp = first_name <=> that.first_name; cmp != 0)
+
{{source|1=
      return cmp;
+
a == b ? std::partial_ordering::equivalent :
  return tax_id <=> that.tax_id;
+
a < b  ? std::partial_ordering::less :
  }
+
b < a  ? std::partial_ordering::greater :
// ... non-comparison functions ...
+
        std::partial_ordering::unordered
};
+
// compiler generates all four relational operators
+
TotallyOrdered to1, to2;
+
std::set<TotallyOrdered> s; // ok
+
s.insert(to1); // ok
+
if (to1 <= to2) { /*...*/ } // ok, single call to <=>
+
 
}}
 
}}
  
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.
+
====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}}.
  
===Weak ordering===
+
For each subobject {{c|x_i}} in the [[#Default comparison order|(expanded) subobject list]] for {{c|x}}:
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
+
# 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.
  
{{source|1=
+
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>}}.
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
+
====Non-placeholder return type====
CaseInsensitiveString cis1, cis2;
+
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)}}).
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 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.
if (cis1 <= "xyzzy") { /*...*/ } // ok, performs one comparison operation
+
if ("xyzzy" >= cis1) { /*...*/ } // ok, identical semantics
+
}}
+
  
Note that this example demonstrates the effect a heterogeneous operator<=> has: it generates heterogeneous comparisons in both directions.
+
====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.
  
===Partial ordering===
+
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}}.
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:
+
* 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).
{{source|1=
+
* Otherwise, the return value is {{c|static_cast<R>(std::strong_ordering::equal)}}.
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
+
}}
+
  
===Defaulted three-way comparison===
+
{{example
The default operator<=> performs lexicographical comparison by successively comparing
+
|code=
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:
+
#include <compare>
{{source|1=
+
#include <iostream>
for /*each base or member subobject o of T*/
+
#include <set>
  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.
+
struct Point
 
+
{
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:
+
    int x;
{{source|1=
+
    int y;
template<class T1, class T2>
+
    auto operator<=>(const Point&) const = default;
struct P {
+
    /* non-comparison functions */
T1 x1;
+
T2 x2;
+
friend auto operator<=>(const P&, const P&) = default;
+
 
};
 
};
 +
 +
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
 +
}
 
}}
 
}}
  
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.
+
===Equality comparison===
 +
====Explicit declaration====
 +
An {{c/core|1=operator==}} for a class type can be defined as defaulted with return type {{c/core|bool}}.
  
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
+
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.
  
===Defaulted equality comparison===
+
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.
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:
+
The comparison result between {{c|x_i}} and {{c|y_i}} is the result of {{c|1=x_i == y_i}}.
 +
* 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}}.
  
{{example|code=
+
{{example
struct Point {
+
|code=
  int x;
+
#include <iostream>
  int y;
+
 
  bool operator==(const Point&) const = default;
+
struct Point
  // ... non-comparison functions ...
+
{
 +
    int x;
 +
    int y;
 +
    bool operator==(const Point&) const = default;
 +
    /* non-comparison functions */
 
};
 
};
// compiler generates element-wise equality testing
 
  
#include <iostream>
+
int main()
int main() {
+
{
  Point pt1{3, 5}, pt2{2, 5};
+
    Point pt1{3, 5}, pt2{2, 5};
  std::cout << std::boolalpha
+
    std::cout << std::boolalpha
    << (pt1 != pt2) << '\n'  // true
+
        << (pt1 != pt2) << '\n'  // true
    << (pt1 == pt1) << '\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===
+
====Implicit declaration====
Any of the four relational operators can be explicitly defaulted. A defaulted relational operator must have the return type {{c|bool}}.
+
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:
 +
* The {{rlp|declarations#Declarators|declarator identifier}} is replaced with {{c/core|1=operator==}}.
 +
* The return type is replaced with {{c/core|bool}}.
 +
 
 +
{{source|1=
 +
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;
 +
};
 +
}}
 +
 
 +
===Secondary comparison===
 +
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}}.
 +
 
 +
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.
 +
* 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.
 +
* 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.
 +
 
 +
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}}.
  
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:
 
 
{{source|1=
 
{{source|1=
 
struct HasNoRelational {};
 
struct HasNoRelational {};
  
struct C {
+
struct C
  friend HasNoRelational operator<=>(const C&, const C&);
+
{
  bool operator<(const C&) = default;                       // ok, function is deleted
+
    friend HasNoRelational operator<=>(const C&, const C&);
 +
    bool operator<(const C&) const = default; // OK, function is defaulted
 
};
 
};
 
}}
 
}}
  
Defaulting of 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 the {{c|1=operator<=>}}.
+
===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#Relational_operators|Operator overloading}} for relational operators
+
* {{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