Comparison Concepts

Date: 2018-09-24
Author: David Stone (davidmstone@google.com, david@doublewise.net)
Audience: LEWG

Proposal

This paper proposes allowing anything which is currently specified as accepting a BinaryPredicate to also accept a function that returns a comparison category implicitly convertible to weak_equality. Anything which is currently specified as accepting a Compare should also accept a function that returns a comparison category implicitly convertible to weak_ordering.

This paper also proposes a general library requirement that if a <=> b is valid, it is consistent with all other comparison operators. In other words, for every comparison operator (==, !=, <, <=, >, and >=) and for every value of a and b, (a @ b) == (a <=> b @ 0). In particular, any function that accepts a type meeting the LessThanComparable concept should be able to call a <=> b instead of a < b if a <=> b is well-formed. Whether an implementation calls <=> or any of the 6 traditional comparison operators is not considered an observable difference.

This paper does not recommend changing the definitions of the comparable type concepts (such as LessThanComparable) to require operator<=>. operator<=> is a valid implementation strategy to satisfy these concepts, but should not be required, as all that we care about for these concepts is the direct comparisons. This ensures backward compatibility with old user-defined types.

Motivation

This is especially important in the case of Compare to maximize efficiency. We would like users to be able to pass in function objects that are three-way-comparison-aware. Right now, std::map<std::string, T, Compare> has to make two passes over each string it compares if that string does not compare less. The operator<=> proposal gives us a more efficient way to do things (guaranteed single pass), so we should take advantage of it where possible. This paper does not propose changing the default template argument for map, but this paper does allow users to create something more efficient.

For BinaryPredicate, the issue has more to do with consistency. If a user has written a function returning a comparison category, we know that they intend it to be used to compare objects. It is a frustrating and incomplete user experience to not be able to use that function object consistently. This is especially true if the user is trying to perform multiple operations on the same type of elements. For instance, they may want to write a comparison function that can be passed to both sort and unique. This is not possible right now, because sort requires a Compare, but unique requires a BinaryPredicate. By integrating comparison categories into these concepts, we allow a single function object to meet both requirements.

Background

There are two sets of concepts in the standard library that relate to comparisons: concepts that constrain comparison function objects, and concepts that constrain comparable types.

Comparison Function Objects

BinaryPredicate

A BinaryPredicate must accept two arguments and return a value contextually convertible to bool. representing whether those two objects are equal. BinaryPredicate is used in the following places in the standard:

Compare.

A Compare must accept two arguments and return a bool representing whether the first argument is less than the second, and it must provide a strict weak order (a < b implies !(b < a), and equality is determined by !(a < b) and !(b < a)). Compare is used in the following places in the standard:

Comparable Type Concepts