<!DOCTYPE html><html><head><meta charset="utf-8"><title>library-operator-spaceship</title><style></style></head><body id="preview">
<h1><a id="Effect_of_operator_on_the_C_Standard_Library_0"></a>Effect of <code>operator&lt;=&gt;</code> on the C++ Standard Library</h1>
<p>Document Number: P0790R0<br>
Date: 2017-10-06<br>
Author: David Stone (<a href="/cdn-cgi/l/email-protection#56322522393833636616343a39393b3433243178383322"><span class="__cf_email__" data-cfemail="a0c4d3d4cfcec59590e0c2cccfcfcdc2c5d2c78ecec5d4">[email&#160;protected]</span></a>, <a href="/cdn-cgi/l/email-protection#660207100f0226020913040a03110f150348080312"><span class="__cf_email__" data-cfemail="01656077686541656e74636d64766872642f6f6475">[email&#160;protected]</span></a>)<br>
Audience: LEWG</p>
<p>This paper is to evaluate what changes we should make to the C++ standard library in response to <a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r1.pdf">http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r1.pdf</a>, which adds <code>operator&lt;=&gt;</code> to the language.</p>
<p>My general algorithm to determine what operations to support are: If the type represents a value of some sort, it should at least be weak_equality. If the type has some sort of meaningful ordering, it should have weak_ordering. We should be cautious in giving polymorphic types <code>operator&lt;=&gt;</code> (but if they already have other comparison operators, then we might as well).</p>
<p>This document should contain a complete list of types or categories of types in C++.</p>
<h2><a id="Backward_Compatibility_15"></a>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. This has the unfortunate effect that moving from the old style to the new is a breaking change, but the situations in which anything is broken is rare. I believe it is limited to users forming a pointer to a function that points at one of these comparison operators, which requires the user to know whether the function was implemented as a member or a free function. If it was implemented as a friend function and it was defined inline, users cannot take the address of that function, further limiting the scope of the breakage. 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.</p>
<h2><a id="Types_that_should_not_get_operator_20"></a>Types that should not get <code>operator&lt;=&gt;</code></h2>
<ul>
<li>exception types</li>
<li>tag classes? (nothrow, piecewise_construct_t, etc.)</li>
<li>arithmetic function objects (plus, minus, etc.)</li>
<li>comparison function objects (equal_to, etc.)</li>
<li>owner_less</li>
<li>logical function objects (logical_and, etc.)</li>
<li>bitwise function objects (bit_and, etc.)</li>
<li>nested_exception</li>
<li>allocator_traits</li>
<li>char_traits</li>
<li>iterator_traits</li>
<li>numeric_limits</li>
<li>pointer_traits</li>
<li>regex_traits</li>
<li>chrono::duration_values</li>
<li>tuple_element</li>
<li>max_align_t</li>
<li>map::node_type</li>
<li>map::insert_return_type</li>
<li>set::node_type</li>
<li>set::insert_return_type</li>
<li>unordered_map::node_type</li>
<li>unordered_map::insert_return_type</li>
<li>unordered_set::node_type</li>
<li>unordered_set::insert_return_type</li>
<li>any</li>
<li>default_delete</li>
<li>aligned_storage</li>
<li>aligned_union</li>
<li>system_clock</li>
<li>steady_clock</li>
<li>high_resolution_clock</li>
<li>locale::facet</li>
<li>locale::id</li>
<li>ctype_base</li>
<li>ctype</li>
<li>ctype_byname</li>
<li>codecvt_base</li>
<li>codecvt</li>
<li>codecvt_byname</li>
<li>num_get</li>
<li>num_put</li>
<li>numpunct</li>
<li>numpunct_byname</li>
<li>collate</li>
<li>collate_byname</li>
<li>time_get</li>
<li>time_get_byname</li>
<li>time_put</li>
<li>time_put_byname</li>
<li>money_base</li>
<li>money_get</li>
<li>money_put</li>
<li>money_punct</li>
<li>moneypunct_byname</li>
<li>message_base</li>
<li>messages</li>
<li>messages_byname</li>
<li>FILE</li>
<li>va_list</li>
<li>back_insert_iterator</li>
<li>front_insert_iterator</li>
<li>insert_iterator</li>
<li>ostream_iterator</li>
<li>ostreambuf_iterator</li>
<li>ios_base</li>
<li>ios_base::Init</li>
<li>basic_ios</li>
<li>basic_streambuf</li>
<li>basic_istream</li>
<li>basic_iostream</li>
<li>basic_ostream</li>
<li>basic_stringbuf</li>
<li>basic_istringstream</li>
<li>basic_ostringstream</li>
<li>basic_stringstream</li>
<li>basic_filebuf</li>
<li>basic_ifstream</li>
<li>basic_ofstream</li>
<li>basic_fstream</li>
<li>atomic_flag</li>
<li>thread</li>
<li>mutex</li>
<li>recursive_mutex</li>
<li>timed_mutex</li>
<li>timed_recursive_mutex</li>
<li>lock_guard</li>
<li>scoped_lock</li>
<li>unique_lock</li>
<li>once_flag</li>
<li>shared_mutex</li>
<li>shared_timed_mutex</li>
<li>shared_lock</li>
<li>condition_variable</li>
<li>condition_variable_any</li>
<li>promise</li>
<li>future</li>
<li>shared_future</li>
<li>packaged_task</li>
<li>random_device</li>
<li>hash</li>
<li>weak_ptr</li>
<li>basic_regex</li>
<li>sequential_execution_policy</li>
<li>parallel_execution_policy</li>
<li>parallel_vector_execution_policy</li>
<li>default_searcher?</li>
<li>boyer_moore_searcher?</li>
<li>boyer_moore_horspool_searcher?</li>
<li>initializer_list: <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 initializer lists containing the same set of values compared as not equal. Therefore, I recommend not defining it for this type without a separate paper advocating for one particular set of semantics.</li>
</ul>
<h2><a id="Types_that_get_their_operator_from_a_conversion_operator_134"></a>Types that get their <code>operator&lt;=&gt;</code> from a conversion operator</h2>
<p>These types would get <code>operator&lt;=&gt;</code> if possible already. Do we want to define an explicit operator or just rely on the conversion?</p>
<ul>
<li>integral_constant (has <code>operator T</code>) and all types deriving from integral_constant</li>
<li>bitset::reference (has <code>operator bool</code>)</li>
<li>reference_wrapper (has <code>operator T</code>)</li>
<li>atomic (has <code>operator T</code>)</li>
</ul>
<h2><a id="Types_that_should_get_operator_no_change_from_current_comparisons_144"></a>Types that should get <code>operator&lt;=&gt;</code>, no change from current comparisons</h2>
<ul>
<li>error_category: strong_ordering</li>
<li>error_code: strong_ordering</li>
<li>error_condition: strong_ordering</li>
<li>exception_ptr: strong_ordering</li>
<li>type_info: strong_equality (see <code>type_index</code> for a strong comparison category type)</li>
<li>monostate: strong_ordering</li>
<li>bitset: strong_equality (should this be strong_ordering?)</li>
<li>allocator: strong_equality</li>
<li>unique_ptr: strong_ordering, heterogeneous with T1, D1 vs T2, D2 and nullptr_t</li>
<li>shared_ptr: strong_ordering, heterogeneous with T1 vs T2 and nullptr_t</li>
<li>memory_resource: strong_equality</li>
<li>polymorphic_allocator: strong_equality</li>
<li>synchronized_pool_resource: (from memory_resource base)</li>
<li>unsynchronized_pool_resource: (from memory_resource base)</li>
<li>monotonic_buffer_resource: (from memory_resource base)</li>
<li>scoped_allocator_adaptor: strong_equality</li>
<li>pool_options: strong_equality</li>
<li>function: strong_equality with nullptr_t only (no homogenous operator)</li>
<li>chrono::duration: strong_ordering, heterogeneous</li>
<li>chrono::time_point: strong_ordering, heterogeneous in the duration</li>
<li>type_index: strong_ordering</li>
<li>basic_string: (strong|weak)_ordering (depends on CharT, heterogeneous with CharT const *)</li>
<li>basic_string_view: (strong|weak)_ordering (depends on CharT)</li>
<li>locale: strong_equality</li>
<li>complex: (strong|weak)_equality (heterogeneous with T)</li>
<li>linear_congruential_engine: strong_equality</li>
<li>mersenne_twister_engine: strong_equality</li>
<li>subtract_with_carry_engine: strong_equality</li>
<li>discard_block_engine: strong_equality</li>
<li>independent_bits_engine: strong_equality</li>
<li>shuffle_order_engine: strong_equality</li>
<li>seed_seq: no * (strong_equality)</li>
<li>uniform_int_distribution: strong_equality</li>
<li>uniform_int_distribution::param_type: strong_equality</li>
<li>uniform_real_distribution: strong_equality</li>
<li>uniform_real_distribution::param_type: strong_equality</li>
<li>bernoulli_distribution: strong_equality</li>
<li>bernoulli_distribution::param_type: strong_equality</li>
<li>binomial_distribution: strong_equality</li>
<li>binomial_distribution::param_type: strong_equality</li>
<li>geometric_distribution: strong_equality</li>
<li>geometric_distribution::param_type: strong_equality</li>
<li>negative_binomial_distribution: strong_equality</li>
<li>negative_binomial_distribution::param_type: strong_equality</li>
<li>poisson_distribution: strong_equality</li>
<li>poisson_distribution::param_type: strong_equality</li>
<li>exponential_distribution: strong_equality</li>
<li>exponential_distribution::param_type: strong_equality</li>
<li>gamma_distribution: strong_equality</li>
<li>gamma_distribution::param_type: strong_equality</li>
<li>weibull_distribution: strong_equality</li>
<li>weibull_distribution::param_type: strong_equality</li>
<li>extreme_value_distribution: strong_equality</li>
<li>extreme_value_distribution::param_type: strong_equality</li>
<li>normal_distribution: strong_equality</li>
<li>normal_distribution::param_type: strong_equality</li>
<li>lognormal_distribution: strong_equality</li>
<li>lognormal_distribution::param_type: strong_equality</li>
<li>chi_squared_distribution: strong_equality</li>
<li>chi_squared_distribution::param_type: strong_equality</li>
<li>cauchy_distribution: strong_equality</li>
<li>cauchy_distribution::param_type: strong_equality</li>
<li>fisher_f_distribution: strong_equality</li>
<li>fisher_f_distribution::param_type: strong_equality</li>
<li>student_t_distribution: strong_equality</li>
<li>student_t_distribution::param_type: strong_equality</li>
<li>discrete_distribution: strong_equality</li>
<li>discrete_distribution::param_type: strong_equality</li>
<li>piecewsie_constant_distribution: strong_equality</li>
<li>piecewsie_constant_distribution::param_type: strong_equality</li>
<li>piecewise_linear_distribution: strong_equality</li>
<li>piecewise_linear_distribution::param_type: strong_equality</li>
<li>filesystem::path: strong_ordering</li>
<li>filesystem::path::iterator: strong_ordering</li>
<li>filesystem::directory_entry: strong_ordering</li>
<li>filesystem::directory_iterator: strong_ordering</li>
<li>filesystem::recursive_directory_iterator: strong_ordering</li>
<li>istream_iterator: heterogeneous strong_equality</li>
<li>istreambuf_iterator: heterogeneous strong_equality</li>
<li>match_results: strong_equality</li>
<li>regex_iterator: strong_equality</li>
<li>regex_token_iterator: strong_equality</li>
<li>thread::id: strong_ordering</li>
<li>fpos: strong_equality</li>
<li>array::iterator: strong_ordering</li>
<li>deque::iterator: strong_ordering</li>
<li>forward_list::iterator: strong_equality</li>
<li>list::iterator: strong_equality</li>
<li>vector::iterator: strong_ordering</li>
<li>map::iterator: strong_equality</li>
<li>set::iterator: strong_equality</li>
<li>multimap::iterator: strong_equality</li>
<li>multiset::iterator: strong_equality</li>
<li>unordered_map::iterator: strong_equality</li>
<li>unodered_set::iterator: strong_equality</li>
<li>unordered_multimap::iterator: strong_equality</li>
<li>unodered_multiset::iterator: strong_equality</li>
<li>valarray::iterator: strong_ordering</li>
</ul>
<h2><a id="Types_that_should_get_operator_and_they_wrap_another_type_245"></a>Types that should get <code>operator&lt;=&gt;</code> and they wrap another type</h2>
<p>These should all just return the strongest ordering supported by all types that they wrap:</p>
<ul>
<li>array</li>
<li>deque</li>
<li>forward_list</li>
<li>list</li>
<li>vector (including vector&lt;bool&gt;)</li>
<li>map</li>
<li>set</li>
<li>multimap</li>
<li>multiset</li>
<li>unordered_map</li>
<li>unodered_set</li>
<li>unodered_multimap</li>
<li>unordered_multiset</li>
<li>queue</li>
<li>queue::iterator</li>
<li>priority_queue</li>
<li>priority_queue::iterator</li>
<li>stack</li>
<li>stack::iterator</li>
<li>pair</li>
<li>tuple</li>
<li>reverse_iterator</li>
<li>move_iterator</li>
<li>sub_match: weak_ordering heterogeneous with basic_string_view (cannot pick up from base object pair, as sub_match only compares one of the sub-objects)</li>
<li>optional, including heterogeneous <code>operator&lt;=&gt;</code> with <code>optional&lt;T&gt; &lt;=&gt; optional&lt;U&gt;</code>, <code>optional&lt;T&gt; &lt;=&gt; U</code>, <code>optional&lt;T&gt; &lt;=&gt; nullopt_t</code>, and <code>nullopt_t &lt;=&gt; nullopt_t</code></li>
<li>variant:<pre><code>    // + 1 to make valueless_by_exception ordered lowest
    if (auto cmp = (lhs.index() + 1 &lt;=&gt; rhs.index() + 1); cmp != 0) {
        return cmp;
    }
    return std::get&lt;lhs.index()&gt;(lhs) &lt;=&gt; std::get&lt;rhs.index()&gt;(rhs);
</code></pre>
</li>
</ul>
<h2><a id="Types_that_should_get_operator_that_have_no_comparisons_now_283"></a>Types that should get <code>operator&lt;=&gt;</code> that have no comparisons now</h2>
<ul>
<li>enable_shared_from_this: strong_ordering to allow derived classes to <code>= default</code></li>
<li>integer_sequence: strong_equality</li>
<li>ratio: We should create heterogeneous <code>operator&lt;=&gt;</code> on values instead of being forced to use <code>ratio_equal</code> on types</li>
<li>filesystem::file_status: strong_equality</li>
<li>filesystem::space_info: strong_equality</li>
<li>slice: strong_equality</li>
<li>slice_array: strong_equality</li>
<li>gslice: strong_equality</li>
<li>gslice_array: strong_equality</li>
<li>mask_array: strong_equality</li>
<li>indirect_array: strong_equality</li>
<li>to_chars_result: strong_equality</li>
<li>from_chars_result: strong_equality</li>
<li>nullptr_t: strong_ordering. It may be surprising that nullptr_t does not have any comparison operators already. It is comparable with many other types, and  edg, gcc, and Visual Studio provide all comparison operators. clang, meanwhile, provides only <code>nullptr == nullptr</code> and <code>nullptr != nullptr</code>.</li>
</ul>
<h3><a id="Types_from_C_that_should_get_operator_300"></a>Types from C that should get <code>operator&lt;=&gt;</code></h3>
<ul>
<li>div_t, ldiv_t, lldiv_t, imaxdiv_t: strong_ordering</li>
<li>timespec: strong_ordering</li>
<li>tm: strong_ordering</li>
<li>lconv: strong_equality</li>
<li>fenv_t: strong_equality</li>
<li>fpos_t: strong_ordering</li>
<li>mbstate_t: strong_equality</li>
</ul>
<h2><a id="valarray_310"></a>valarray</h2>
<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 would make sense to provide some sort of function that returns <code>valarray&lt;comparison_category&gt;</code>. Consistency with current <code>valarray</code> behavior suggests we name that function <code>operator&lt;=&gt;</code>, but consistency with <code>operator&lt;=&gt;</code> suggests we name it anything else.</p>
<h2><a id="New_Functions__Function_Objects_315"></a>New Functions / Function Objects</h2>
<ul>
<li>owner_compare: by analogy with <code>owner_less</code> for <code>shared_ptr</code> and <code>weak_ptr</code>. Do we follow the pattern of <code>owner_less</code> and make the type a template, or do we just make its <code>operator()</code> a template? If we are planning on deprecating <code>owner_less</code>, I would recommend making <code>operator()</code> a template so we do not have to do the &quot;<code>owner_less&lt;&gt;</code> is generic&quot; business. Returns <code>strong_ordering</code>.</li>
<li>shared_ptr::owner_compare: See above.</li>
<li>weak_ptr::owner_compare: See above.</li>
</ul>
<h2><a id="Updating_Comparison_Concepts_322"></a>Updating Comparison Concepts</h2>
<h3><a id="Components_based_on_equality_324"></a>Components based on equality</h3>
<p>All of these components require a function that tests for equality and do so via a function matching the concept <code>BinaryPredicate</code>. This is a complete set of operations that make use of the <code>BinaryPredicate</code> concept. We should extend <code>BinaryPredicate</code> to also allow a function returning <code>weak_equality</code> or better.</p>
<ul>
<li>find_end</li>
<li>find_first_of</li>
<li>adjacent_find</li>
<li>mismatch</li>
<li>equal</li>
<li>search</li>
<li>search_n</li>
<li>unique</li>
<li>unique_copy</li>
<li>default_searcher</li>
<li>boyer_moore_searcher</li>
<li>boyer_moore_horspool_searcher</li>
<li>UnorderedAssociativeContainer</li>
</ul>
<h3><a id="Components_based_on_ordering_343"></a>Components based on ordering</h3>
<p>All of these components require a strict weak ordering and do so via a function matching the concept <code>Compare</code>. This is a complete set of operations that make use of the <code>Compare</code> concept. We should extend <code>Compare</code> to also allow a function returning <code>weak_ordering</code> or better. If any of these components (for instance, <code>map</code>) are passed an instance of <code>Compare</code> returning an ordering, it can then use a single call to the comparison function to determine equivalence, rather than relying on <code>!comp(lhs, rhs) and !comp(rhs, lhs)</code>.</p>
<ul>
<li>sort</li>
<li>stable_sort</li>
<li>partial_sort</li>
<li>partial_sort_copy</li>
<li>is_sorted</li>
<li>is_sorted_until</li>
<li>nth_element</li>
<li>lower_bound</li>
<li>upper_bound</li>
<li>equal_range</li>
<li>binary_search</li>
<li>merge</li>
<li>inplace_merge</li>
<li>includes</li>
<li>set_union</li>
<li>set_intersection</li>
<li>set_difference</li>
<li>set_symmetric_difference</li>
<li>push_heap</li>
<li>pop_heap</li>
<li>make_heap</li>
<li>sort_heap</li>
<li>is_heap</li>
<li>is_heap_until</li>
<li>min</li>
<li>max</li>
<li>minmax</li>
<li>min_element</li>
<li>max_element</li>
<li>minmax_element</li>
<li>clamp</li>
<li>lexicographical_compare</li>
<li>next_permutation</li>
<li>prev_permutation</li>
<li>forward_list::merge</li>
<li>forward_list::sort</li>
<li>list::merge</li>
<li>list::sort</li>
<li>AssociativeContainer</li>
<li>priority_queue</li>
</ul>
<h3><a id="Open_Questions_389"></a>Open Questions</h3>
<p>When we specify generic type concepts, do we specify in terms of <code>operator&lt;=&gt;</code>? If we do so, it means that functions that accept a UDT corresponding to some concept no longer match that concept. As an example, the <code>RandomAccessIterator</code> concept requires all six operators to be present. At some point in the future, do we want to change this requirement to <code>weak_ordering operator&lt;=&gt;</code>? Same with weaker iterator concepts, do we want <code>weak_equality</code> or stronger? The following are the minimal mappings I believe each concept requires. Note that by applying <code>operator&lt;=&gt;</code>, we frequently end up with more operators than just those minimally required by the existing concept</p>
<ul>
<li>EqualityComparable: <code>weak_equality</code></li>
<li>LessThanComparable: <code>weak_ordering</code> (this would exclude floating point values, as <code>LessThanComparable</code> requires a strict weak ordering)</li>
<li>NullablePointer: inherits <code>EqualityComparable</code>, additionally requires <code>!=</code> (works by default with <code>weak_equality</code>). Requires heterogeneous <code>weak_equality</code> with <code>nullptr_t</code>.</li>
<li>InputIterator: inherits <code>EqualityComparable</code>, additionally requires <code>!=</code> (works by default with <code>weak_equality</code>).</li>
<li>Allocator: <code>weak_equality</code>, heterogeneous with the equivalent <code>Allocator</code> for other types</li>
<li>“bitmask type”: strong_(equality|ordering) (it might be a bitset, which only supports equality, it might be integer / enum, which supports ordering, but this will naturally fall out, no wording changes needed)</li>
<li>We redundantly specify <code>weak_ordering</code> for TrivialClock::rep, TrivialClock::duration, and TrivialClock::time_point, but that is implied because we know they are an arithmetic type, a chrono::duration, and a chrono::time_point, respectively.</li>
</ul>
<h2><a id="Deprecation_402"></a>Deprecation</h2>
<p>We should deprecate or remove existing comparison operators for types that do not defer to user-defined types (for instance, <code>allocator</code>) but which get <code>operator&lt;=&gt;</code>.</p>
<p>Types that do wrap user-defined types (<code>vector</code>, <code>optional</code>, etc.) should provide only <code>operator&lt;=&gt;</code> if the underlying type defines <code>operator&lt;=&gt;</code>. If the underlying type does not define <code>operator&lt;=&gt;</code>, we should provide only the old operators (do we deprecate this support?). Following this guidance when the “user-defined” type is actually a standard type (<code>vector&lt;string&gt;</code>) or when the underlying type is a built-in type leads to a slight backward incompatibility. We could provide deprecated explicit operator overloads to prevent this.</p>
<p>We should deprecate existing three-way comparison functions:</p>
<ul>
<li>basic_string::compare</li>
<li>basic_string_view::compare</li>
<li>filesystem::path::compare</li>
<li>sub_match::compare</li>
</ul>
<p>We should deprecate rel_ops. We should also remove the provision that states that “In this library, whenever a declaration is provided for an operator!=, operator&gt;, operator&gt;=, or operator&lt;=, and requirements and semantics are not explicitly provided, the requirements and semantics are as specified in this Clause.” in reference to rel_ops.</p>
<p>We should not add <code>operator&lt;=&gt;</code> to any deprecated types.</p>
<h2><a id="Miscellaneous_420"></a>Miscellaneous</h2>
<p>All <code>operator&lt;=&gt;</code> should be constexpr noexcept 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>. I do not believe there are any unspecified result types for which we currently guarantee any comparison operators are present.</p>

<script style="display: none !important;">!function(e,t,r,n,c,a,l){function i(t,r){return r=e.createElement('div'),r.innerHTML='<a href="'+t.replace(/"/g,'&quot;')+'"></a>',r.childNodes[0].getAttribute('href')}function o(e,t,r,n){for(r='',n='0x'+e.substr(t,2)|0,t+=2;t<e.length;t+=2)r+=String.fromCharCode('0x'+e.substr(t,2)^n);return i(r)}try{for(c=e.getElementsByTagName('a'),l='/cdn-cgi/l/email-protection#',n=0;n<c.length;n++)try{(t=(a=c[n]).href.indexOf(l))>-1&&(a.href='mailto:'+o(a.href,t+l.length))}catch(e){}for(c=e.querySelectorAll('.__cf_email__'),n=0;n<c.length;n++)try{(a=c[n]).parentNode.replaceChild(e.createTextNode(o(a.getAttribute('data-cfemail'),0)),a)}catch(e){}}catch(e){}}(document);</script></body></html>