Effect of operator<=> on the C++ Standard Library

Document Number: P0790R0
Date: 2017-10-06
Author: David Stone ([email protected], [email protected])
Audience: LEWG

This paper is to evaluate what changes we should make to the C++ standard library in response to http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r1.pdf, which adds operator<=> to the language.

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 operator<=> (but if they already have other comparison operators, then we might as well).

This document should contain a complete list of types or categories of types in C++.

Backward Compatibility

The operator<=> proposal was written such that the “generated” operators are equivalent to source code rewrites – there is no actual operator== 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 a @ b is valid; these cases are not broken by such a change because users could not have depended on it, anyway.

Types that should not get operator<=>

Types that get their operator<=> from a conversion operator

These types would get operator<=> if possible already. Do we want to define an explicit operator or just rely on the conversion?

Types that should get operator<=>, no change from current comparisons

Types that should get operator<=> and they wrap another type

These should all just return the strongest ordering supported by all types that they wrap:

Types that should get operator<=> that have no comparisons now

Types from C that should get operator<=>

valarray

Current comparison operators return a valarray<bool>, giving you the result for each pair (with undefined behavior for differently-sized valarray arguments). It would make sense to provide some sort of function that returns valarray<comparison_category>. Consistency with current valarray behavior suggests we name that function operator<=>, but consistency with operator<=> suggests we name it anything else.

New Functions / Function Objects

Updating Comparison Concepts

Components based on equality

All of these components require a function that tests for equality and do so via a function matching the concept BinaryPredicate. This is a complete set of operations that make use of the BinaryPredicate concept. We should extend BinaryPredicate to also allow a function returning weak_equality or better.

Components based on ordering

All of these components require a strict weak ordering and do so via a function matching the concept Compare. This is a complete set of operations that make use of the Compare concept. We should extend Compare to also allow a function returning weak_ordering or better. If any of these components (for instance, map) are passed an instance of Compare returning an ordering, it can then use a single call to the comparison function to determine equivalence, rather than relying on !comp(lhs, rhs) and !comp(rhs, lhs).

Open Questions

When we specify generic type concepts, do we specify in terms of operator<=>? 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 RandomAccessIterator concept requires all six operators to be present. At some point in the future, do we want to change this requirement to weak_ordering operator<=>? Same with weaker iterator concepts, do we want weak_equality or stronger? The following are the minimal mappings I believe each concept requires. Note that by applying operator<=>, we frequently end up with more operators than just those minimally required by the existing concept

Deprecation

We should deprecate or remove existing comparison operators for types that do not defer to user-defined types (for instance, allocator) but which get operator<=>.

Types that do wrap user-defined types (vector, optional, etc.) should provide only operator<=> if the underlying type defines operator<=>. If the underlying type does not define operator<=>, 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 (vector<string>) 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.

We should deprecate existing three-way comparison functions:

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>, operator>=, or operator<=, and requirements and semantics are not explicitly provided, the requirements and semantics are as specified in this Clause.” in reference to rel_ops.

We should not add operator<=> to any deprecated types.

Miscellaneous

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

When we list a result type as “unspecified” it is unspecified whether it has operator<=>. I do not believe there are any unspecified result types for which we currently guarantee any comparison operators are present.