Namespaces
Variants
Views
Actions

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

From cppreference.com
< cpp‎ | language
m (link to ja)
(P1185R2 (incomplete), P1959R0)
Line 4: Line 4:
 
Provides a way to request the compiler to generate consistent relational operators for a class.
 
Provides a way to request the compiler to generate consistent relational operators for a class.
  
In brief, a class that defines {{c|1=operator<=>}} automatically gets compiler-generated operators ==, !=, <, <=, >, and >=. A class can define {{c|1=operator<=>}} as defaulted, in which case the compiler will also generate the code for that operator.
+
In brief, a class that defines {{c|1=operator<=>}} automatically gets compiler-generated operators <, <=, >, and >=. A class can define {{c|1=operator<=>}} as defaulted, in which case the compiler will also generate the code for that operator.
  
 
{{source|1=
 
{{source|1=
Line 14: Line 14:
 
  // ... non-comparison functions ...
 
  // ... non-comparison functions ...
 
};
 
};
// compiler generates all six relational operators
+
// compiler generates all four relational operators
 
Point pt1, pt2;
 
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok
 
 
std::set<Point> s; // ok
 
std::set<Point> s; // ok
 
s.insert(pt1); // ok
 
s.insert(pt1); // ok
 
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to <=>
 
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to <=>
 
}}
 
}}
 +
 +
{{todo|Defaulted equality comparisons}}
  
 
==Custom comparisons==
 
==Custom comparisons==
 
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<=>}}.
 
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 five available return types:
+
There are three available return types:
 
{| class="wikitable" style="font-size:85%; text-align:center;"
 
{| class="wikitable" style="font-size:85%; text-align:center;"
 
|-
 
|-
Line 47: Line 48:
 
| distinguishable
 
| distinguishable
 
| allowed
 
| allowed
|-
 
| {{ltt|cpp/utility/compare/strong_equality|std::strong_equality}}
 
| == !=
 
| indistinguishable
 
| not allowed
 
|-
 
| {{ltt|cpp/utility/compare/weak_equality|std::weak_equality}}
 
| == !=
 
| distinguishable
 
| not allowed
 
 
|}
 
|}
  
Line 77: Line 68:
 
  // ... non-comparison functions ...
 
  // ... non-comparison functions ...
 
};
 
};
// compiler generates all 6 relational operators
+
// compiler generates all four relational operators
 
TotallyOrdered to1, to2;
 
TotallyOrdered to1, to2;
if (to1 == to2) { /*...*/ } // ok
 
 
std::set<TotallyOrdered> s; // ok
 
std::set<TotallyOrdered> s; // ok
 
s.insert(to1); // ok
 
s.insert(to1); // ok
Line 103: Line 93:
 
};
 
};
  
// Compiler generates all six relational operators
+
// Compiler generates all four relational operators
 
CaseInsensitiveString cis1, cis2;
 
CaseInsensitiveString cis1, cis2;
if (cis1 == cis2) { /*...*/ } // ok
+
std::set<CaseInsensitiveString> s; // ok
set<CaseInsensitiveString> s; // ok
+
 
s.insert(/*...*/); // ok
 
s.insert(/*...*/); // ok
 
if (cis1 <= cis2) { /*...*/ } // ok, performs one comparison operation
 
if (cis1 <= cis2) { /*...*/ } // ok, performs one comparison operation
Line 130: Line 119:
 
   // ... non-comparison functions ...
 
   // ... non-comparison functions ...
 
};
 
};
// compiler generates all six relational operators
+
// compiler generates all four relational operators
 
PersonInFamilyTree per1, per2;
 
PersonInFamilyTree per1, per2;
if (per1 == per2) { /*...*/ } // ok, per1 is per2
+
if (per1 < per2) { /*...*/ } // ok, per2 is an ancestor of per1
else if (per1 < per2) { /*...*/ } // ok, per2 is an ancestor of per1
+
 
else if (per1 > per2) { /*...*/ } // ok, per1 is an ancestor of per2
 
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
 
else { /*...*/ } // per1 and per2 are unrelated
 
if (per1 <= per2) { /*...*/ } // ok, per2 is per1 or an ancestor of per1
 
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 (per1 >= per2) { /*...*/ } // ok, per1 is per2 or an ancestor of per2
if (per1 != per2) { /*...*/ } // ok, per1 is not per2
+
if (std::is_neq(per1 <=> per2)) { /*...*/ } // ok, per1 is not per2
}}
+
 
+
===Strong equality===
+
There are many types for which equality makes sense, but not less-than ordering: a common example are the complex numbers, or any pair of numbers in general:
+
{{source|1=
+
class EqualityComparable {
+
  std::string name;
+
  BigInt number1;
+
  BigInt number2;
+
public:
+
  std::strong_equality operator<=>(const EqualityComparable& that) const {
+
    if (auto cmp = number1 <=> that.number1; cmp != 0) return cmp;
+
    if (auto cmp = number2 <=> that.number2; cmp != 0) return cmp;
+
    return name <=> that.name;
+
  }
+
};
+
// compiler generates == and !=, but not < > <=, or >=
+
EqualityComparable ec1, ec2;
+
if (ec1 != ec2) { /*...*/ } // ok
+
}}
+
 
+
===Weak equality===
+
In this example, two values that compare equal under this comparison (which is case-insensitive on the member {{tt|name}} can be distinguished by functions that are case-sensitive:
+
{{source|1=
+
class EquivalenceComparable {
+
  CaseInsensitiveString name;
+
  BigInt number1;
+
  BigInt number2;
+
public:
+
  std::weak_equality operator<=>(const EquivalenceComparable& that) const {
+
    if (auto cmp = number1 <=> that.number1; cmp != 0) return cmp;
+
    if (auto cmp = number2 <=> that.number2; cmp != 0) return cmp;
+
    return name <=> that.name;
+
}
+
// ... non-comparison functions ...
+
};
+
// compiler generates != and ==, but not <, >, <=, or >=
+
EquivalenceComparable ec1, ec2;
+
if (ec1 != ec2) { /*...*/ } // ok
+
 
}}
 
}}
  
Line 201: Line 151:
 
}}
 
}}
  
Otherwise, the return type must be one of the five 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.
+
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.
  
 
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
 
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
  
 
===Defaulted two-way comparisons===
 
===Defaulted two-way comparisons===
Any of the six two-way relational operators can be explicitly defaulted. A defaulted relational operator must have the return type {{c|bool}}.
+
Any of the four relational operators can be explicitly defaulted. A defaulted relational operator must have the return type {{c|bool}}.
  
 
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:
 
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 C {
 
struct C {
   friend std::strong_equality operator<=>(const C&, const C&);
+
   friend HasNoRelational operator<=>(const C&, const C&);
  friend bool operator==(const C& x, const C& y) = default; // ok, returns x <=> y == 0
+
 
   bool operator<(const C&) = default;                      // ok, function is deleted
 
   bool operator<(const C&) = default;                      // ok, function is deleted
 
};
 
};

Revision as of 03:47, 25 November 2019

 
 
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
 
 

Provides a way to request the compiler to generate consistent relational operators for a class.

In brief, a class that defines operator<=> automatically gets compiler-generated operators <, <=, >, and >=. A class can define operator<=> as defaulted, in which case the compiler will also generate the code for that operator.

class Point {
 int x;
 int y;
public:
 auto operator<=>(const Point&) const = default;
 // ... non-comparison functions ...
};
// compiler generates all four relational operators
Point pt1, pt2;
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to <=>

Contents

Custom comparisons

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 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 operator<=>.

There are three available return types:

Return type Operators Equivalent values are.. Incomparable values are..
std::strong_ordering == != < > <= >= indistinguishable not allowed
std::weak_ordering == != < > <= >= distinguishable not allowed
std::partial_ordering == != < > <= >= distinguishable allowed

Strong ordering

An example of a custom operator<=> that returns 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)

class 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
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 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.

Weak ordering

An example of a custom operator<=> that returns 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

class CaseInsensitiveString {
  std::string s;
public:
  std::weak_ordering operator<=>(const CaseInsensitiveString& b) const {
    return case_insensitive_compare(s.c_str(), b.s.c_str());
  }
  std::weak_ordering operator<=>(const char* b) const {
    return case_insensitive_compare(s.c_str(), b);
  }
  // ... non-comparison functions ...
};
 
// Compiler generates all four relational operators
CaseInsensitiveString cis1, cis2;
std::set<CaseInsensitiveString> s; // ok
s.insert(/*...*/); // ok
if (cis1 <= cis2) { /*...*/ } // ok, performs one comparison operation
 
// Compiler also generates all 12 heterogeneous relational operators
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.

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:

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

The default operator<=> performs lexicographical comparison by successively comparing 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:

for /*each base or member subobject o of T*/
  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.

If the declared return type is auto, then the actual return type is 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:

template<class T1, class T2>
struct P {
 T1 x1;
 T2 x2;
 friend auto operator<=>(const P&, const P&) = default;
};

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.

The defaulted operator<=> is implicitly deleted and returns 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

Defaulted two-way comparisons

Any of the four relational operators can be explicitly defaulted. A defaulted relational operator must have the return type bool.

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 x <=> y @ 0 if an operator<=> with the original order of parameters was selected by overload resolution, or 0 @ y <=> x otherwise:

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

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 operator<=>.

See also