<h1 id="effectofoperatoronthecstandardlibrary">Effect of <code>operator&lt;=&gt;</code> on the C++ Standard Library</h1>

<pre>
Document Number: P0790R2
Date: 2019-01-21
Author: David Stone (&#100;&#97;&#118;&#105;&#100;&#109;&#115;&#116;&#111;&#110;&#101;&#64;&#103;&#111;&#111;&#103;&#108;&#101;&#46;&#99;&#111;&#109;, &#100;&#97;&#118;&#105;&#100;&#64;&#100;&#111;&#117;&#98;&#108;&#101;&#119;&#105;&#115;&#101;&#46;&#110;&#101;&#116;)
Audience: LEWG, LWG
</pre>

<p>This paper lists (what are expected to be) non-controversial changes to the C++ standard library in response to <a href="https://wg21.link/P0515">P0515</a>, which adds <code>operator&lt;=&gt;</code> to the language. This is expected to be non-controversial because it tries to match existing behavior as much as possible. As a result, all proposed additions are either <code>strong_equality</code> or <code>strong_ordering</code>, matching the existing comparison operators.</p>

<p>This document should contain a complete list of types or categories of types in C++.</p>

<h2 id="revisionhistory">Revision History</h2>

<p>R1: A much broader version of this paper was presented to LEWG at a previous meeting. What remains in this paper is everything which the group did not find controversial and which probably does not require significant justification. All controversial aspects will be submitted in separate papers.
R2: Added wording.</p>

<h2 id="backwardcompatibility">Backward Compatibility</h2>

<p>The <code>operator&lt;=&gt;</code> proposal was written such that the "generated" operators are equivalent to source code rewrites – there is no actual <code>operator==</code> that a user could take the address of. Users are not allowed to form pointers to standard library member functions and are unable to form pointers to friend functions defined inline in the class. There are some cases where we do not specify how the operator was implemented, only that the expression <code>a @ b</code> is valid; these cases are not broken by such a change because users could not have depended on it, anyway. In general, we accept changes that overload existing functions, which also has the effect of breaking code which takes the address of a free function.</p>

<h2 id="typesthatarenotproposedtogetoperatorinthispaper">Types that are not proposed to get <code>operator&lt;=&gt;</code> in this paper</h2>

<p>These types are not comparable now. This paper does not propose adding any new comparisons to any of these types.</p>

<ul>
<li>deprecated types</li>

<li>exception types</li>

<li>tag classes (<code>nothrow</code>, <code>piecewise_construct_t</code>, etc.)</li>

<li>arithmetic function objects (<code>plus</code>, <code>minus</code>, etc.)</li>

<li>comparison function objects (<code>equal_to</code>, etc.)</li>

<li><code>owner_less</code></li>

<li>logical function objects (<code>logical_and</code>, etc.)</li>

<li>bitwise function objects (<code>bit_and</code>, etc.)</li>

<li><code>nested_exception</code></li>

<li><code>allocator_traits</code></li>

<li><code>pool_options</code></li>

<li><code>char_traits</code></li>

<li><code>iterator_traits</code></li>

<li><code>numeric_limits</code></li>

<li><code>pointer_traits</code></li>

<li><code>regex_traits</code></li>

<li><code>chrono::duration_values</code></li>

<li><code>tuple_element</code></li>

<li><code>max_align_t</code></li>

<li><code>map::node_type</code></li>

<li><code>map::insert_return_type</code></li>

<li><code>set::node_type</code></li>

<li><code>set::insert_return_type</code></li>

<li><code>unordered_map::node_type</code></li>

<li><code>unordered_map::insert_return_type</code></li>

<li><code>unordered_set::node_type</code></li>

<li><code>unordered_set::insert_return_type</code></li>

<li><code>any</code></li>

<li><code>default_delete</code></li>

<li><code>aligned_storage</code></li>

<li><code>aligned_union</code></li>

<li><code>system_clock</code></li>

<li><code>steady_clock</code></li>

<li><code>high_resolution_clock</code></li>

<li><code>locale::facet</code></li>

<li><code>locale::id</code></li>

<li><code>ctype_base</code></li>

<li><code>ctype</code></li>

<li><code>ctype_byname</code></li>

<li><code>codecvt_base</code></li>

<li><code>codecvt</code></li>

<li><code>codecvt_byname</code></li>

<li><code>num_get</code></li>

<li><code>num_put</code></li>

<li><code>numpunct</code></li>

<li><code>numpunct_byname</code></li>

<li><code>collate</code></li>

<li><code>collate_byname</code></li>

<li><code>time_get</code></li>

<li><code>time_get_byname</code></li>

<li><code>time_put</code></li>

<li><code>time_put_byname</code></li>

<li><code>money_base</code></li>

<li><code>money_get</code></li>

<li><code>money_put</code></li>

<li><code>money_punct</code></li>

<li><code>moneypunct_byname</code></li>

<li><code>message_base</code></li>

<li><code>messages</code></li>

<li><code>messages_byname</code></li>

<li><code>FILE</code></li>

<li><code>va_list</code></li>

<li><code>back_insert_iterator</code></li>

<li><code>front_insert_iterator</code></li>

<li><code>insert_iterator</code></li>

<li><code>ostream_iterator</code></li>

<li><code>ostreambuf_iterator</code></li>

<li><code>ios_base</code></li>

<li><code>ios_base::Init</code></li>

<li><code>basic_ios</code></li>

<li><code>basic_streambuf</code></li>

<li><code>basic_istream</code></li>

<li><code>basic_iostream</code></li>

<li><code>basic_ostream</code></li>

<li><code>basic_stringbuf</code></li>

<li><code>basic_istringstream</code></li>

<li><code>basic_ostringstream</code></li>

<li><code>basic_stringstream</code></li>

<li><code>basic_filebuf</code></li>

<li><code>basic_ifstream</code></li>

<li><code>basic_ofstream</code></li>

<li><code>basic_fstream</code></li>

<li><code>gslice</code></li>

<li><code>slice_array</code></li>

<li><code>gslice_array</code></li>

<li><code>mask_array</code></li>

<li><code>indirect_array</code></li>

<li><code>atomic_flag</code></li>

<li><code>thread</code></li>

<li><code>mutex</code></li>

<li><code>recursive_mutex</code></li>

<li><code>timed_mutex</code></li>

<li><code>timed_recursive_mutex</code></li>

<li><code>lock_guard</code></li>

<li><code>scoped_lock</code></li>

<li><code>unique_lock</code></li>

<li><code>once_flag</code></li>

<li><code>shared_mutex</code></li>

<li><code>shared_timed_mutex</code></li>

<li><code>shared_lock</code></li>

<li><code>condition_variable</code></li>

<li><code>condition_variable_any</code></li>

<li><code>promise</code></li>

<li><code>future</code></li>

<li><code>shared_future</code></li>

<li><code>packaged_task</code></li>

<li><code>random_device</code></li>

<li><code>hash</code></li>

<li><code>weak_ptr</code></li>

<li><code>basic_regex</code></li>

<li><code>sequential_execution_policy</code></li>

<li><code>parallel_execution_policy</code></li>

<li><code>parallel_vector_execution_policy</code></li>

<li><code>default_searcher</code></li>

<li><code>boyer_moore_searcher</code></li>

<li><code>boyer_moore_horspool_searcher</code></li>

<li><code>ratio</code></li>

<li><code>integer_sequence</code></li>

<li><code>seed_seq</code> (paper needed to add <code>strong_equality</code>)</li>

<li><code>enable_shared_from_this</code>: It would be nice to give it a <code>strong_ordering</code> to allow derived classes to <code>= default</code>. However, this means that all classes that do not explicitly delete their comparison operator get an <code>operator&lt;=&gt;</code> that compares only the <code>enable_shared_from_this</code> base class, which is almost certainly wrong. Since this is intended to be used as a base class, we should not add <code>operator&lt;=&gt;</code> to it. Moreover, classes which <code>enable_shared_from_this</code> are unlikely to be basic value classes so they do not lose much by not being able to default.</li>

<li><code>initializer_list</code>: <code>initializer_list</code> is a reference type. It would be strange to give it reference semantics on copy but value semantics for comparison. It would also be surprising if two <code>initializer_list</code> containing the same set of values compared as not equal. Therefore, I recommend not defining it for this type.</li>
</ul>

<h3 id="typesfromcthatarenotproposedtogetoperatorinthispaper">Types from C that are not proposed to get <code>operator&lt;=&gt;</code> in this paper</h3>

<ul>
<li><code>div_t</code></li>

<li><code>ldiv_t</code></li>

<li><code>lldiv_t</code></li>

<li><code>imaxdiv_t</code></li>

<li><code>timespec</code></li>

<li><code>tm</code></li>

<li><code>lconv</code></li>

<li><code>fenv_t</code></li>

<li><code>fpos_t</code></li>

<li><code>mbstate_t</code></li>
</ul>

<h3 id="typesthathaveonlyandandthusdonotrequire">Types that have only <code>==</code> and <code>!=</code>, and thus do not require <code>&lt;=&gt;</code></h3>

<ul>
<li><code>type_info</code></li>

<li><code>bitset</code> (paper would be needed to change this to <code>strong_ordering</code>)</li>

<li><code>allocator</code></li>

<li><code>memory_resource</code></li>

<li><code>synchronized_pool_resource</code>: (implicitly from <code>memory_resource</code> base class)</li>

<li><code>unsynchronized_pool_resource</code>: (implicitly from <code>memory_resource</code> base class)</li>

<li><code>monotonic_buffer_resource</code>: (implicitly from <code>memory_resource</code> base class)</li>

<li><code>polymorphic_allocator</code></li>

<li><code>scoped_allocator_adaptor</code></li>

<li><code>function</code> with <code>nullptr_t</code> only (no homogenous operator)</li>

<li><code>locale</code></li>

<li><code>complex</code> (heterogeneous with <code>T</code> and homogeneous)</li>

<li><code>linear_congruential_engine</code></li>

<li><code>mersenne_twister_engine</code></li>

<li><code>subtract_with_carry_engine</code></li>

<li><code>discard_block_engine</code></li>

<li><code>independent_bits_engine</code></li>

<li><code>shuffle_order_engine</code></li>

<li><code>uniform_int_distribution</code></li>

<li><code>uniform_int_distribution::param_type</code></li>

<li><code>uniform_real_distribution</code></li>

<li><code>uniform_real_distribution::param_type</code></li>

<li><code>bernoulli_distribution</code></li>

<li><code>bernoulli_distribution::param_type</code></li>

<li><code>binomial_distribution</code></li>

<li><code>binomial_distribution::param_type</code></li>

<li><code>geometric_distribution</code></li>

<li><code>geometric_distribution::param_type</code></li>

<li><code>negative_binomial_distribution</code></li>

<li><code>negative_binomial_distribution::param_type</code></li>

<li><code>poisson_distribution</code></li>

<li><code>poisson_distribution::param_type</code></li>

<li><code>exponential_distribution</code></li>

<li><code>exponential_distribution::param_type</code></li>

<li><code>gamma_distribution</code></li>

<li><code>gamma_distribution::param_type</code></li>

<li><code>weibull_distribution</code></li>

<li><code>weibull_distribution::param_type</code></li>

<li><code>extreme_value_distribution</code></li>

<li><code>extreme_value_distribution::param_type</code></li>

<li><code>normal_distribution</code></li>

<li><code>normal_distribution::param_type</code></li>

<li><code>lognormal_distribution</code></li>

<li><code>lognormal_distribution::param_type</code></li>

<li><code>chi_squared_distribution</code></li>

<li><code>chi_squared_distribution::param_type</code></li>

<li><code>cauchy_distribution</code></li>

<li><code>cauchy_distribution::param_type</code></li>

<li><code>fisher_f_distribution</code></li>

<li><code>fisher_f_distribution::param_type</code></li>

<li><code>student_t_distribution</code></li>

<li><code>student_t_distribution::param_type</code></li>

<li><code>discrete_distribution</code></li>

<li><code>discrete_distribution::param_type</code></li>

<li><code>piecewsie_constant_distribution</code></li>

<li><code>piecewsie_constant_distribution::param_type</code></li>

<li><code>piecewise_linear_distribution</code></li>

<li><code>piecewise_linear_distribution::param_type</code></li>

<li><code>istream_iterator</code></li>

<li><code>istreambuf_iterator</code></li>

<li><code>filesystem::path::iterator</code></li>

<li><code>filesystem::directory_iterator</code></li>

<li><code>filesystem::recursive_directory_iterator</code></li>

<li><code>match_results</code></li>

<li><code>regex_iterator</code></li>

<li><code>regex_token_iterator</code></li>

<li><code>fpos</code></li>

<li><code>forward_list::iterator</code></li>

<li><code>list::iterator</code></li>

<li><code>map::iterator</code></li>

<li><code>set::iterator</code></li>

<li><code>multimap::iterator</code></li>

<li><code>multiset::iterator</code></li>

<li><code>unordered_map::iterator</code></li>

<li><code>unodered_set::iterator</code></li>

<li><code>unordered_multimap::iterator</code></li>

<li><code>unodered_multiset::iterator</code></li>
</ul>

<h2 id="typesthatshouldgetwithareturntypeofstrong_orderingnochangefromcurrentcomparisons">Types that should get <code>&lt;=&gt;</code> with a return type of <code>strong_ordering</code>, no change from current comparisons</h2>

<p>These types are all currently comparable.</p>

<ul>
<li><code>error_category</code></li>

<li><code>error_code</code></li>

<li><code>error_condition</code></li>

<li><code>exception_ptr</code></li>

<li><code>monostate</code></li>

<li><code>chrono::duration</code>: heterogeneous with durations of other representations and periods</li>

<li><code>chrono::time_point</code>: heterogeneous in the duration</li>

<li><code>type_index</code></li>

<li><code>filesystem::path</code></li>

<li><code>filesystem::directory_entry</code></li>

<li><code>thread::id</code></li>

<li><code>array::iterator</code></li>

<li><code>deque::iterator</code></li>

<li><code>vector::iterator</code></li>

<li><code>valarray::iterator</code></li>
</ul>

<h2 id="typesthatwillgettheiroperatorfromaconversionoperator">Types that will get their <code>operator&lt;=&gt;</code> from a conversion operator</h2>

<p>These types will get <code>operator&lt;=&gt;</code> if possible without any changes, just like they already have whatever comparison operators their underlying type has.</p>

<ul>
<li><code>integral_constant</code> and all types deriving from <code>integral_constant</code> (has <code>operator T</code>)</li>

<li><code>bitset::reference</code> (has <code>operator bool</code>)</li>

<li><code>reference_wrapper</code> (has <code>operator T &amp;</code>)</li>

<li><code>atomic</code> (has <code>operator T</code>)</li>
</ul>

<p>This has the disadvantage that types which have a template comparison operator will not have their wrapper convertible. For instance, <code>std::reference_wrapper&lt;std::string&gt;</code> is not currently comparable. This does not affect <code>bitset::reference</code>, as it has a fixed conversion to <code>bool</code>, but it does affect the other three.</p>

<h2 id="typesthatwrapanothertype">Types that wrap another type</h2>

<ul>
<li><code>array</code></li>

<li><code>deque</code></li>

<li><code>forward_list</code></li>

<li><code>list</code></li>

<li><code>vector</code> (including <code>vector&lt;bool&gt;</code>)</li>

<li><code>map</code></li>

<li><code>set</code></li>

<li><code>multimap</code></li>

<li><code>multiset</code></li>

<li><code>unordered_map</code></li>

<li><code>unodered_set</code></li>

<li><code>unodered_multimap</code></li>

<li><code>unordered_multiset</code></li>

<li><code>queue</code></li>

<li><code>queue::iterator</code></li>

<li><code>priority_queue</code></li>

<li><code>priority_queue::iterator</code></li>

<li><code>stack</code></li>

<li><code>stack::iterator</code></li>

<li><code>pair</code></li>

<li><code>tuple</code></li>

<li><code>reverse_iterator</code></li>

<li><code>move_iterator</code></li>

<li><code>optional</code></li>

<li><code>variant</code></li>
</ul>

<p>This turned out to be much more complicated than expected and will require its own paper.</p>

<h3 id="basic_stringbasic_string_viewchar_traitsandsub_match"><code>basic_string</code>, <code>basic_string_view</code>, <code>char_traits</code>, and <code>sub_match</code></h3>

<p>Properly integrating <code>operator&lt;=&gt;</code> with these types requires more thought than this paper has room for, and thus will be discussed separately.</p>

<h3 id="unique_ptrandshared_ptr"><code>unique_ptr</code> and <code>shared_ptr</code></h3>

<p>They contain state that is not observed in the comparison operators. Therefore, they will get their own paper.</p>

<h3 id="valarray">valarray</h3>

<p>Current comparison operators return a <code>valarray&lt;bool&gt;</code>, giving you the result for each pair (with undefined behavior for differently-sized <code>valarray</code> arguments). It might make sense to provide some sort of function that returns <code>valarray&lt;comparison_category&gt;</code>, but that should not be named <code>operator&lt;=&gt;</code>. This paper does not suggest adding <code>operator&lt;=&gt;</code> to <code>valarray</code>.</p>

<h2 id="typesthathavenocomparisonsnowbutarebeingproposedtogetoperatorinanotherpaper">Types that have no comparisons now but are being proposed to get <code>operator&lt;=&gt;</code> in another paper</h2>

<p>This paper does not propose changing any of the following types -- they are here only for completeness.</p>

<ul>
<li><code>filesystem::file_status</code></li>

<li><code>filesystem::space_info</code></li>

<li><code>slice</code></li>

<li><code>to_chars_result</code></li>

<li><code>from_chars_result</code></li>
</ul>

<h2 id="nullptr_t"><code>nullptr_t</code></h2>

<p>Already supports <code>strong_equality</code> in the working draft. I will be writing a separate paper proposing <code>strong_ordering</code>.</p>

<h2 id="notupdatingconceptsthatprovidecomparisons">Not Updating Concepts That Provide Comparisons</h2>

<p>This category includes things like <code>BinaryPredicate</code> and <code>Compare</code>. This is addressed in a separate paper.</p>

<h2 id="notupdatingconceptsthatrequirecomparisons">Not Updating Concepts That Require Comparisons</h2>

<p>This includes things like <code>LessThanComparable</code> and <code>EqualityComparable</code>. This is addressed in a separate paper.</p>

<h2 id="miscellaneous">Miscellaneous</h2>

<p>All <code>operator&lt;=&gt;</code> should be <code>constexpr</code> and <code>noexcept</code> where possible, following the lead of the language feature and allowing <code>= default</code> as an implementation strategy for some types.</p>

<p>When we list a result type as "unspecified" it is unspecified whether it has <code>operator&lt;=&gt;</code>. There are not any unspecified result types for which we currently guarantee any comparison operators are present, so there is no extra work to do here.</p>

<h2 id="wording">Wording</h2>

<p>All wording is relative to <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4791.pdf">N4791</a>.</p>

<h3 id="generalwording">General wording</h3>

<p>15.4.2.3  Operators [operators]</p>

<p><del>1 In this library, whenever a declaration is provided for an <code>operator!=</code>, <code>operator&gt;</code>, <code>operator&lt;=</code>, or <code>operator&gt;=</code> for a type <code>T</code>, its requirements and semantics are as follows, unless explicitly specified otherwise.</p>

<pre><code>bool operator!=(const T&amp; x, const T&amp; y);
</code></pre>

<p>2 Requires: Type <code>T</code> is <code>Cpp17EqualityComparable</code> (Table 23).</p>

<p>3 Returns: <code>!(x == y)</code>.</p>

<pre><code>bool operator&gt;(const T&amp; x, const T&amp; y);
</code></pre>

<p>4 Requires: Type <code>T</code> is <code>Cpp17LessThanComparable</code> (Table 24).</p>

<p>5 Returns: <code>y &lt; x</code>.</p>

<pre><code>bool operator&lt;=(const T&amp; x, const T&amp; y);
</code></pre>

<p>6 Requires: Type <code>T</code> is <code>Cpp17LessThanComparable</code> (Table 24).</p>

<p>7 Returns: <code>!(y &lt; x)</code>.</p>

<pre><code>bool operator&gt;=(const T&amp; x, const T&amp; y);
</code></pre>

<p>8 Requires: Type <code>T</code> is <code>Cpp17LessThanComparable</code> (Table 24).</p>

<p>9 Returns: <code>!(x &lt; y)</code>.</del></p>

<p><ins>1 Unless specified otherwise, if <code>lhs</code> and <code>rhs</code> are values of types from this library, the following shall hold:</p>

<ul>
<li><code>lhs != rhs</code> is a valid expression if and only if <code>lhs == rhs</code> is a valid expression.</li>

<li><code>lhs &lt; rhs</code>, <code>lhs &gt; rhs</code>, <code>lhs &lt;= rhs</code>, and <code>lhs &gt;= rhs</code> are all valid expressions if and only if <code>lhs &lt;=&gt; rhs</code> is a valid expression.</li>

<li>If <code>lhs &lt;=&gt; rhs</code> is a valid expression, <code>lhs == rhs</code> is a valid expression.</li>
</ul>

<p>The requirements and semantics of these operators are as follows, unless explicitly specified otherwise.</p>

<p>| Expression | Return type | Operational semantics |
| ---------- | ---------- | ---------- |
| <code>lhs &lt;=&gt; rhs</code> | <code>std::strong_ordering</code> | If <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_ordering::equal</code>, the two objects have an equivalence relation as described in Cpp17EqualityComparable ([utility.arg.requirements]). Regardless of the return value, <code>lhs &lt;=&gt; rhs</code> is a total ordering relation (24.7). |
| <code>lhs == rhs</code> | Convertible to <code>bool</code> | <code>lhs &lt;=&gt; rhs == 0</code> |
| <code>lhs != rhs</code> | Convertible to <code>bool</code> | <code>!(lhs == rhs)</code> |
| <code>lhs &lt; rhs</code> | Convertible to <code>bool</code> | <code>lhs &lt;=&gt; rhs &lt; 0</code> |
| <code>lhs &gt; rhs</code> | Convertible to <code>bool</code> | <code>lhs &lt;=&gt; rhs &gt; 0</code> |
| <code>lhs &lt;= rhs</code> | Convertible to <code>bool</code> | <code>lhs &lt;=&gt; rhs &lt;= 0</code> |
| <code>lhs &gt;= rhs</code> | Convertible to <code>bool</code> | <code>lhs &lt;=&gt; rhs &gt;= 0</code> |</p>

<h3 id="error_category"><code>error_category</code></h3>

<p>18.5.2.3 Non-virtual members [syserr.errcat.nonvirtuals]
constexpr error<em>category() noexcept;
1 Effects: Constructs an object of class error</em>category.</p>

<p><del>    bool operator==(const error<em>category&amp; rhs) const noexcept;
2 Returns: <code>this == &amp;rhs</code>.
    bool operator!=(const error</em>category&amp; rhs) const noexcept;
3 Returns: <code>!(*this == rhs)</code>.
    bool operator&lt;(const error_category&amp; rhs) const noexcept;
4 Returns: <code>less&lt;const error_category*&gt;()(this, &amp;rhs)</code>.
[Note: <code>less</code> (19.14.7) provides a total ordering for pointers. — end note]</del>
<ins>18.5.2.4 Comparisons [syserr.errcat.comparisons]</p>

<p>For two values <code>lhs</code> and <code>rhs</code> of type <code>error_category</code>, the expression <code>lhs &lt;=&gt; rhs</code> is of type <code>std::strong_­ordering</code>. If the address of <code>lhs</code> and <code>rhs</code> compare equal ([expr.eq]), <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_­ordering::equal</code>; if <code>lhs</code> and <code>rhs</code> compare unequal, <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_­ordering::less</code> if <code>std::less{}(&amp;lhs, &amp;rhs)</code> is <code>true</code> ([comparisons]), otherwise yields <code>std::strong_ordering::greater</code>.</ins></p>

<p>18.5.2.<del>4</del><ins>5</ins> Program-defined classes derived from error_category [syserr.errcat.derived]</p>

<p>[...]</p>

<p>18.5.2.<del>5</del><ins>6</ins> Error category objects [syserr.errcat.objects]</p>

<h3 id="error_codeanderror_condition"><code>error_code</code> and <code>error_condition</code></h3>

<p>18.5.5 Comparison functions [syserr.compare]
<del>    bool operator==(const error<em>code&amp; lhs, const error</em>code&amp; rhs) noexcept;</p>

<p>1 Returns: <code>lhs.category() == rhs.category() &amp;&amp; lhs.value() == rhs.value()</code></p>

<pre><code>bool operator==(const error_code&amp; lhs, const error_condition&amp; rhs) noexcept;
</code></pre>

<p>2 Returns: <code>lhs.category().equivalent(lhs.value(), rhs) || rhs.category().equivalent(lhs, rhs.value())</code></p>

<pre><code>bool operator==(const error_condition&amp; lhs, const error_code&amp; rhs) noexcept;
</code></pre>

<p>3 Returns: <code>rhs.category().equivalent(rhs.value(), lhs) || lhs.category().equivalent(rhs, lhs.value())</code></p>

<pre><code>bool operator==(const error_condition&amp; lhs, const error_condition&amp; rhs) noexcept;
</code></pre>

<p>4 Returns: <code>lhs.category() == rhs.category() &amp;&amp; lhs.value() == rhs.value()</code></p>

<pre><code>bool operator!=(const error_code&amp; lhs, const error_code&amp; rhs) noexcept;
bool operator!=(const error_code&amp; lhs, const error_condition&amp; rhs) noexcept;
bool operator!=(const error_condition&amp; lhs, const error_code&amp; rhs) noexcept;
bool operator!=(const error_condition&amp; lhs, const error_condition&amp; rhs) noexcept;
</code></pre>

<p>5 Returns: <code>!(lhs == rhs)</code>.</p>

<pre><code>bool operator&lt;(const error_code&amp; lhs, const error_code&amp; rhs) noexcept;
</code></pre>

<p>6 Returns: <code>lhs.category() &lt; rhs.category() || (lhs.category() == rhs.category() &amp;&amp; lhs.value() &lt; rhs.value())</code></p>

<pre><code>bool operator&lt;(const error_condition&amp; lhs, const error_condition&amp; rhs) noexcept;
</code></pre>

<p>7 Returns: <code>lhs.category() &lt; rhs.category() || (lhs.category() == rhs.category() &amp;&amp; lhs.value() &lt; rhs.value())</code></del>
<ins>For values <code>lhs_code</code> and <code>rhs_code</code> of type <code>error_code</code> and values <code>lhs_condition</code> and <code>rhs_condition</code> of type <code>error_condition</code>, the following holds:</p>

<ol>
<li>The expression <code>lhs_code &lt;=&gt; rhs_code</code> is of type <code>std::strong_­ordering</code>. Equivalent to <code>std::tie(lhs_code.category(), lhs_code.value()) &lt;=&gt; std::tie(rhs_code.category(), rhs_code.value())</code>.</li>

<li>The expression <code>lhs_condition &lt;=&gt; rhs_condition</code> is of type <code>std::strong_­ordering</code>. Equivalent to <code>std::tie(lhs_condition.category(), lhs_condition.value()) &lt;=&gt; std::tie(rhs_condition.category(), rhs_condition.value())</code>.</li>

<li>The expression <code>lhs_code == rhs_condition</code> is of type <code>bool</code>. Equivalent to <code>lhs_code.category().equivalent(lhs_code.value(), rhs_condition) || rhs_condition.category().equivalent(lhs_code, rhs_category.value())</code>.</li>
</ol>

<p>[Note: The function <code>tie</code> is defined in Clause 19.5. — end note]</ins></p>

<h3 id="monostate"><code>monostate</code></h3>

<p>19.7.9 monostate relational operators [variant.monostate.relops]</p>

<p><del>    constexpr bool operator==(monostate, monostate) noexcept { return true; }
    constexpr bool operator!=(monostate, monostate) noexcept { return false; }
    constexpr bool operator&lt;(monostate, monostate) noexcept { return false; }
    constexpr bool operator>(monostate, monostate) noexcept { return false; }
    constexpr bool operator&lt;=(monostate, monostate) noexcept { return true; }
    constexpr bool operator>=(monostate, monostate) noexcept { return true; }</del>
<ins>For two values <code>lhs</code> and <code>rhs</code> of type <code>monostate</code>, the expression <code>lhs &lt;=&gt; rhs</code> is of type <code>std::strong_­ordering</code> and yields <code>std::strong_­ordering::equal</code>.</ins></p>

<p>[Note: monostate objects have only a single state; they thus always compare equal. — end note]</p>

<h3 id="type_index"><code>type_index</code></h3>

<p>19.17.2 type_index overview [type.index.overview]</p>

<pre><code>namespace std {
    class type_index {
    public:
        type_index(const type_info&amp; rhs) noexcept;
</code></pre>

<p><del>            bool operator==(const type<em>index&amp; rhs) const noexcept;
            bool operator!=(const type</em>index&amp; rhs) const noexcept;
            bool operator&lt; (const type<em>index&amp; rhs) const noexcept;
            bool operator> (const type</em>index&amp; rhs) const noexcept;
            bool operator&lt;= (const type<em>index&amp; rhs) const noexcept;
            bool operator>= (const type</em>index&amp; rhs) const noexcept;</del>
            size<em>t hash</em>code() const noexcept;
            const char* name() const noexcept;
        private:
            const type_info* target;
            // exposition only
            // Note that the use of a pointer here, rather than a reference,
            // means that the default copy/move constructor and assignment
            // operators will be provided and work as expected.
        };
    }</p>

<p>1 The class <code>type_index</code> provides a simple wrapper for <code>type_info</code> which can be used as an index type in associative containers (21.4) and in unordered associative containers (21.5).</p>

<p>19.17.3 <code>type_index</code> members [type.index.members]</p>

<pre><code>type_index(const type_info&amp; rhs) noexcept;
</code></pre>

<p>1 Effects: Constructs a <code>type_index</code> object, the equivalent of <code>target = &amp;rhs</code>.</p>

<p><del>    bool operator==(const type_index&amp; rhs) const noexcept;</p>

<p>2 Returns: <code>*target == *rhs.target</code>.</p>

<pre><code>bool operator!=(const type_index&amp; rhs) const noexcept;
</code></pre>

<p>3 Returns: <code>*target != *rhs.target</code>.</p>

<pre><code>bool operator&lt;(const type_index&amp; rhs) const noexcept;
</code></pre>

<p>4 Returns: <code>target-&gt;before(*rhs.target)</code>.</p>

<pre><code>bool operator&gt;(const type_index&amp; rhs) const noexcept;
</code></pre>

<p>5 Returns: <code>rhs.target-&gt;before(*target)</code>.</p>

<pre><code>bool operator&lt;=(const type_index&amp; rhs) const noexcept;
</code></pre>

<p>6 Returns: <code>!rhs.target-&gt;before(*target)</code>.</p>

<pre><code>bool operator&gt;=(const type_index&amp; rhs) const noexcept;
</code></pre>

<p>7 Returns: <code>!target-&gt;before(*rhs.target)</code>.</del></p>

<pre><code>size_t hash_code() const noexcept;
</code></pre>

<p><del>8</del><ins>2</ins> Returns: <code>target-&gt;hash_code()</code>.</p>

<pre><code>const char* name() const noexcept;
</code></pre>

<p><del>9</del><ins>2</ins> Returns: <code>target-&gt;name()</code>.</p>

<p>19.17.4  Hash support [type.index.hash]</p>

<pre><code>template&lt;&gt; struct hash&lt;type_index&gt;;
</code></pre>

<p>1 For an object <code>index</code> of type <code>type_index</code>, <code>hash&lt;type_index&gt;()(index)</code> shall evaluate to the same result as <code>index.hash_code()</code>.</p>

<p><ins>19.17.5 Comparisons [type.index.comparisons]</p>

<p>For two values <code>lhs</code> and <code>rhs</code> of type <code>type_index</code>, the expression <code>lhs &lt;=&gt; rhs</code> is of type <code>std::strong_­ordering</code>. If  <code>*lhs.target == *rhs.target</code>, <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_­ordering::equal</code>; if <code>lhs</code> and <code>rhs</code> compare unequal, <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_­ordering::less</code> if <code>lhs.target-&gt;before(*rhs.target)</code> is <code>true</code>, otherwise yields <code>std::strong_ordering::greater</code>. [Note: A result of <code>std::strong_ordering::greater</code> happens only in the case where <code>rhs.target-&gt;before(*lhs.target)</code> is <code>true</code>. — end note]</ins></p>

<h3 id="iterators">Iterators</h3>

<p>Amend the requirements table for <code>Cpp17RandomAccessIterator</code> (22.3.5.6) requirements:</p>

<p>| Expression | Return type | Operational semantics | Assertion/note pre-/post-condition |
| ---------- | ---------- | ---------- | ---------- |
| <del>a &lt; b</del> | <del>contextually convertible to <code>bool</code></del> | <del><code>b - a &gt; 0</code></del> | <del><code>&lt;</code> is a total ordering relation</del> |
| <del>a > b</del> | <del>contextually convertible to <code>bool</code></del> | <del><code>b &lt; a</code></del> | <del><code>&gt;</code> is a total ordering relation opposite to <code>&lt;</code>.</del> |
| <del>a >= b</del> | <del>contextually convertible to <code>bool</code></del> | <del><code>!(a &lt; b)</code></del> |  |
| <del>a &lt;= b</del> | <del>contextually convertible to <code>bool</code></del> | <del><code>!(a &gt; b)</code></del> |  |
| <ins>a &lt;=> b</ins> | <ins><code>std::strong_ordering</code></ins> |  | <ins><code>&lt;=&gt;</code> is a total ordering relation</ins> |</p>

<h3 id="chronoduration"><code>chrono::duration</code></h3>

<p>26.5.6  Comparisons [time.duration.comparisons]
1 In the function descriptions that follow, CT represents <code>common_type_t&lt;A, B&gt;</code>, where <code>A</code> and <code>B</code> are the types of the two arguments to the function.</p>

<p><del>    template<class Rep1, class Period1, class Rep2, class Period2>
    constexpr bool operator==(const duration<Rep1, Period1>&amp; lhs, const duration<Rep2, Period2>&amp; rhs);
2 Returns: <code>CT(lhs).count() == CT(rhs).count()</code>.</p>

<pre><code>template&lt;class Rep1, class Period1, class Rep2, class Period2&gt;
constexpr bool operator!=(const duration&lt;Rep1, Period1&gt;&amp; lhs, const duration&lt;Rep2, Period2&gt;&amp; rhs);
</code></pre>

<p>3 Returns: <code>!(lhs == rhs)</code>.</p>

<pre><code>template&lt;class Rep1, class Period1, class Rep2, class Period2&gt;
constexpr bool operator&lt;(const duration&lt;Rep1, Period1&gt;&amp; lhs, const duration&lt;Rep2, Period2&gt;&amp; rhs);
</code></pre>

<p>4 Returns: <code>CT(lhs).count() &lt; CT(rhs).count()</code>.</p>

<pre><code>template&lt;class Rep1, class Period1, class Rep2, class Period2&gt;
constexpr bool operator&gt;(const duration&lt;Rep1, Period1&gt;&amp; lhs, const duration&lt;Rep2, Period2&gt;&amp; rhs);
</code></pre>

<p>5 Returns: <code>rhs &lt; lhs</code>.</p>

<pre><code>template&lt;class Rep1, class Period1, class Rep2, class Period2&gt;
constexpr bool operator&lt;=(const duration&lt;Rep1, Period1&gt;&amp; lhs, const duration&lt;Rep2, Period2&gt;&amp; rhs);
</code></pre>

<p>6 Returns: <code>!(rhs &lt; lhs)</code>.</p>

<pre><code>template&lt;class Rep1, class Period1, class Rep2, class Period2&gt;
constexpr bool operator&gt;=(const duration&lt;Rep1, Period1&gt;&amp; lhs, const duration&lt;Rep2, Period2&gt;&amp; rhs);
</code></pre>

<p>7 Returns: <code>!(lhs &lt; rhs)</code>.</del>
<ins>For two values <code>lhs</code> and <code>rhs</code>, each of which are instances of the class template <code>std::chrono::duration</code> (with possibly different <code>Rep</code> and <code>Period</code> template parameters), the expression <code>lhs &lt;=&gt; rhs</code> is equivalent to <code>CT(lhs).count() &lt;=&gt; CT(rhs).count()</code>. [Note: This implies the operation is <code>constexpr</code> if both <code>lhs</code> and <code>rhs</code> are <code>constexpr</code>. — end note]</ins></p>

<h3 id="chronotime_point"><code>chrono::time_point</code></h3>

<p>26.6.6  Comparisons [time.point.comparisons]</p>

<p><del>    template<class Clock, class Duration1, class Duration2>
    constexpr bool operator==(const time<em>point<Clock, Duration1>&amp; lhs, const time</em>point<Clock, Duration2>&amp; rhs);
1 Returns: <code>lhs.time_since_epoch() == rhs.time_since_epoch()</code>.</p>

<pre><code>template&lt;class Clock, class Duration1, class Duration2&gt;
constexpr bool operator!=(const time_point&lt;Clock, Duration1&gt;&amp; lhs, const time_point&lt;Clock, Duration2&gt;&amp; rhs);
</code></pre>

<p>2 Returns: <code>!(lhs == rhs)</code>.</p>

<pre><code>template&lt;class Clock, class Duration1, class Duration2&gt;
constexpr bool operator&lt;(const time_point&lt;Clock, Duration1&gt;&amp; lhs, const time_point&lt;Clock, Duration2&gt;&amp; rhs);
</code></pre>

<p>3 Returns: <code>lhs.time_since_epoch() &lt; rhs.time_since_epoch()</code>.</p>

<pre><code>template&lt;class Clock, class Duration1, class Duration2&gt;
constexpr bool operator&gt;(const time_point&lt;Clock, Duration1&gt;&amp; lhs, const time_point&lt;Clock, Duration2&gt;&amp; rhs);
</code></pre>

<p>4 Returns: <code>rhs &lt; lhs</code>.
    template<class Clock, class Duration1, class Duration2>
    constexpr bool operator&lt;=(const time<em>point<Clock, Duration1>&amp; lhs, const time</em>point<Clock, Duration2>&amp; rhs);
5 Returns: <code>!(rhs &lt; lhs)</code>.</p>

<pre><code>template&lt;class Clock, class Duration1, class Duration2&gt;
constexpr bool operator&gt;=(const time_point&lt;Clock, Duration1&gt;&amp; lhs, const time_point&lt;Clock, Duration2&gt;&amp; rhs);
</code></pre>

<p>6 Returns: <code>!(lhs &lt; rhs)</code>.</del>
<ins>For two values <code>lhs</code> and <code>rhs</code>, each of which are instances of the class template <code>std::chrono::time_point</code> (with possibly the same <code>Clock</code> template parameter but possibly different <code>Duration</code> template parameters), the expression <code>lhs &lt;=&gt; rhs</code> is equivalent to <code>CT(lhs).time_since_epoch() &lt;=&gt; CT(rhs).time_since_epoch()</code>. [Note: This implies the operation is <code>constexpr</code> if both <code>lhs</code> and <code>rhs</code> are <code>constexpr</code>. — end note]</ins></p>

<h3 id="filesystempath"><code>filesystem::path</code></h3>

<p>Modify the class synopsis (28.11.7  Class <code>path</code> [fs.class.path]) to remove the specification of comparison operators as friend functions:</p>

<pre><code>namespace std::filesystem {
    class path {
    public:
        [...]
</code></pre>

<p><del>            // 28.11.7.7, non-member operators
            friend bool operator==(const path&amp; lhs, const path&amp; rhs) noexcept;
            friend bool operator!=(const path&amp; lhs, const path&amp; rhs) noexcept;
            friend bool operator&lt; (const path&amp; lhs, const path&amp; rhs) noexcept;
            friend bool operator&lt;=(const path&amp; lhs, const path&amp; rhs) noexcept;
            friend bool operator> (const path&amp; lhs, const path&amp; rhs) noexcept;
            friend bool operator>=(const path&amp; lhs, const path&amp; rhs) noexcept;
            friend path operator/ (const path&amp; lhs, const path&amp; rhs);</del>
            [...]
        };
    }</p>

<p>Add the following at the end of 28.11.7.4.8 [fs.path.compare]:</p>

<p><ins>For two values <code>lhs</code> and <code>rhs</code> of type <code>std::filesystem::path</code>, the expression <code>lhs &lt;=&gt; rhs</code> is equivalent to <code>lhs.compare(rhs) &lt;=&gt; 0</code></ins></p>

<p>28.11.7.7  Non-member functions [fs.path.nonmember]</p>

<pre><code>void swap(path&amp; lhs, path&amp; rhs) noexcept;
</code></pre>

<p>1 Effects: Equivalent to <code>lhs.swap(rhs)</code>.</p>

<pre><code>size_t hash_value (const path&amp; p) noexcept;
</code></pre>

<p>2 Returns: A hash value for the path <code>p</code>. If for two paths, <code>p1 == p2</code> then <code>hash_value(p1) == hash_value(p2)</code>.</p>

<p><del>    friend bool operator==(const path&amp; lhs, const path&amp; rhs) noexcept;</p>

<p>3 Returns: <code>!(lhs &lt; rhs) &amp;&amp; !(rhs &lt; lhs)</code>.</p>

<p>4 [Note: Path equality and path equivalence have different semantics.
— (4.1) Equality is determined by the path non-member <code>operator==</code>, which considers the two paths’ lexical representations only. [Example: <code>path("foo") == "bar"</code> is never <code>true</code>. — end example]
— (4.2) Equivalence is determined by the <code>equivalent()</code> non-member function, which determines if two paths resolve (28.11.7) to the same file system entity. [Example: <code>equivalent("foo", "bar")</code> will be <code>true</code> when both paths resolve to the same file. — end example]</p>

<p>Programmers wishing to determine if two paths are “the same” must decide if “the same” means
“the same representation” or “resolve to the same actual file”, and choose the appropriate function
accordingly. — end note]</p>

<pre><code>friend bool operator!=(const path&amp; lhs, const path&amp; rhs) noexcept;
</code></pre>

<p>5 Returns: <code>!(lhs == rhs)</code>.</p>

<pre><code>friend bool operator&lt; (const path&amp; lhs, const path&amp; rhs) noexcept;
</code></pre>

<p>6 Returns: <code>lhs.compare(rhs) &lt; 0</code>.</p>

<pre><code>friend bool operator&lt;=(const path&amp; lhs, const path&amp; rhs) noexcept;
</code></pre>

<p>7 Returns: <code>!(rhs &lt; lhs)</code>.</p>

<pre><code>friend bool operator&gt; (const path&amp; lhs, const path&amp; rhs) noexcept;
</code></pre>

<p>8 Returns: <code>rhs &lt; lhs</code>.</p>

<pre><code>friend bool operator&gt;=(const path&amp; lhs, const path&amp; rhs) noexcept;
</code></pre>

<p>9 Returns: <code>!(lhs &lt; rhs)</code>.</del></p>

<pre><code>friend path operator/ (const path&amp; lhs, const path&amp; rhs);
</code></pre>

<p><del>10</del><ins>3</ins> Effects: Equivalent to: <code>return path(lhs) /= rhs;</code></p>

<h3 id="filesystemdirectory_entry"><code>filesystem::directory_entry</code></h3>

<p>Amend the class synopsis:</p>

<pre><code>namespace std::filesystem {
    class directory_entry {
    public:
        [...]
</code></pre>

<p><del>            bool operator==(const directory<em>entry&amp; rhs) const noexcept;
            bool operator!=(const directory</em>entry&amp; rhs) const noexcept;
            bool operator&lt; (const directory<em>entry&amp; rhs) const noexcept;
            bool operator> (const directory</em>entry&amp; rhs) const noexcept;
            bool operator&lt;=(const directory<em>entry&amp; rhs) const noexcept;
            bool operator>=(const directory</em>entry&amp; rhs) const noexcept;</del></p>

<pre><code>        [...]
    };
}
</code></pre>

<p>Amend 28.11.11.3:</p>

<p><del>    bool operator==(const directory_entry&amp; rhs) const noexcept;</p>

<p>31 Returns: <code>pathobject == rhs.pathobject</code>.</p>

<pre><code>bool operator!=(const directory_entry&amp; rhs) const noexcept;
</code></pre>

<p>32 Returns: <code>pathobject != rhs.pathobject</code>.</p>

<pre><code>bool operator&lt; (const directory_entry&amp; rhs) const noexcept;
</code></pre>

<p>33 Returns: <code>pathobject &lt; rhs.pathobject</code>.</p>

<pre><code>bool operator&gt; (const directory_entry&amp; rhs) const noexcept;
</code></pre>

<p>34 Returns: <code>pathobject &gt; rhs.pathobject</code>.</p>

<pre><code>bool operator&lt;=(const directory_entry&amp; rhs) const noexcept;
</code></pre>

<p>35 Returns: <code>pathobject &lt;= rhs.pathobject</code>.</p>

<pre><code>bool operator&gt;=(const directory_entry&amp; rhs) const noexcept;
</code></pre>

<p>36 Returns: <code>pathobject &gt;= rhs.pathobject</code>.</del></p>

<p><ins>28.11.11.4 Comparisons [fs.dir.entry.comparisons]</p>

<p>For two values <code>lhs</code> and <code>rhs</code> of type <code>std::filesystem::directory_entry</code>, the expression <code>lhs &lt;=&gt; rhs</code> is equivalent to <code>lhs.pathobject &lt;=&gt; rhs.pathobject</code>.</ins></p>

<h3 id="threadid"><code>thread::id</code></h3>

<p>31.3.2.1  Class thread::id [thread.thread.id]</p>

<pre><code>namespace std {
    class thread::id {
    public:
        id() noexcept;
    };
</code></pre>

<p><del>        bool operator==(thread::id x, thread::id y) noexcept;
        bool operator!=(thread::id x, thread::id y) noexcept;
        bool operator&lt;(thread::id x, thread::id y) noexcept;
        bool operator>(thread::id x, thread::id y) noexcept;
        bool operator&lt;=(thread::id x, thread::id y) noexcept;
        bool operator>=(thread::id x, thread::id y) noexcept;</del>
        template<class charT, class traits>
        basic<em>ostream<charT, traits>&amp;
        operator&lt;&lt;(basic</em>ostream<charT, traits>&amp; out, thread::id id);
        // hash support
        template<class T> struct hash;
        template&lt;> struct hash<thread::id>;
    }</p>

<p>1 An object of type <code>thread::id</code> provides a unique identifier for each thread of execution and a single distinct value for all <code>thread</code> objects that do not represent a thread of execution (31.3.2). Each thread of execution has an associated <code>thread::id</code> object that is not equal to the <code>thread::id</code> object of any other thread of execution and that is not equal to the <code>thread::id</code> object of any <code>thread</code> object that does not represent threads of execution.</p>

<p>2 <code>thread::id</code> is a trivially copyable class (10.1). The library may reuse the value of a <code>thread::id</code> of a terminated thread that can no longer be joined.</p>

<p>3 [Note: Relational operators allow <code>thread::id</code> objects to be used as keys in associative containers. — end note]</p>

<pre><code>id() noexcept;
</code></pre>

<p>4 Effects: Constructs an object of type <code>id</code>.</p>

<p>5 Ensures: The constructed object does not represent a thread of execution.</p>

<p><del>    bool operator==(thread::id x, thread::id y) noexcept;</p>

<p>6 Returns: <code>true</code> only if <code>x</code> and <code>y</code> represent the same thread of execution or neither <code>x</code> nor <code>y</code> represents a thread of execution.</p>

<pre><code>bool operator!=(thread::id x, thread::id y) noexcept;
</code></pre>

<p>7 Returns: <code>!(x == y)</code></p>

<pre><code>bool operator&lt;(thread::id x, thread::id y) noexcept;
</code></pre>

<p>8 Returns: A value such that <code>operator&lt;</code> is a total ordering as described in 24.7.</p>

<pre><code>bool operator&gt;(thread::id x, thread::id y) noexcept;
</code></pre>

<p>9 Returns: <code>y &lt; x</code>.</p>

<pre><code>bool operator&lt;=(thread::id x, thread::id y) noexcept;
</code></pre>

<p>10 Returns: <code>!(y &lt; x)</code>.</p>

<pre><code>bool operator&gt;=(thread::id x, thread::id y) noexcept;
</code></pre>

<p>11 Returns: <code>!(x &lt; y)</code>.</del></p>

<pre><code>template&lt;class charT, class traits&gt;
basic_ostream&lt;charT, traits&gt;&amp;
operator&lt;&lt; (basic_ostream&lt;charT, traits&gt;&amp; out, thread::id id);
</code></pre>

<p><del>12</del><ins>6</ins> Effects: Inserts an unspecified text representation of <code>id</code> into <code>out</code>. For two objects of type <code>thread::id</code> <code>x</code> and <code>y</code>, if <code>x == y</code> the <code>thread::id</code> objects have the same text representation and if <code>x != y</code> the <code>thread::id</code> objects have distinct text representations.</p>

<p><del>13</del><ins>7</ins> Returns: <code>out</code>.</p>

<pre><code>template&lt;&gt; struct hash&lt;thread::id&gt;;
</code></pre>

<p><del>14</del><ins>8</ins> The specialization is enabled (19.14.18).</p>

<p><ins>For two values <code>lhs</code> and <code>rhs</code> of type <code>std::thread::id</code>, the expression <code>lhs &lt;=&gt; rhs</code> is of type <code>std::strong_­ordering</code>. If <code>lhs</code> and <code>rhs</code> represent the same thread of execution or neither <code>x</code> nor <code>y</code> represents a thread of execution, <code>lhs &lt;=&gt; rhs</code> yields <code>std::strong_­ordering::equal</code>; if <code>lhs</code> and <code>rhs</code> compare unequal, <code>lhs &lt;=&gt; rhs</code> yields a value consistent with a total order as described in 24.7.</ins></p>
