**Section:** 22.10.8 [comparisons] **Status:** New
**Submitter:** Agustín K-ballo Bergé **Opened:** 2015-04-01 **Last modified:** 2021-04-10 17:22:00 UTC

**Priority: **3

**View other** active issues in [comparisons].

**View all other** issues in [comparisons].

**View all issues with** New status.

**Discussion:**

It is not entirely clear if and when the specializations of `std::less` (and friends) for pointer types
can be used in a constant expression. Consider the following code:

#include <functional> struct foo {}; foo x, y; constexpr bool b = std::less<foo*>{}(&x, &y); // [1] foo z[] = {{}, {}}; constexpr bool ba = std::less<foo*>{}(&z[0], &z[1]); // [2]

Comparing the address of unrelated objects is not a constant expression since the result is unspecified, so
it could be expected for [1] to fail and [2] to succeed. However, `std::less` specialization for pointer
types is well-defined and yields a total order, so it could just as well be expected for [1] to succeed. Finally,
since the implementation of such specializations is not mandated, [2] could fail as well (This could happen, if
an implementation would provide such a specialization and if that would use built-in functions that would not be
allowed in constant expressions, for example). In any case, the standard should be clear so as to avoid
implementation-defined `constexpr`-ness.

*[2017-01-22, Jens provides rationale and proposed wording]*

`std::less<T*>` is required to deliver a total order on pointers.
However, the layout of global objects is typically determined
by the linker, not the compiler, so requiring `std::less<T*>` to
provide an ordering at compile-time that is consistent with
run-time would need results from linking to feed back to
the compiler, something that C++ has traditionally not required.

This wording is relative to N4618.

Add at the end of 22.10.8 [comparisons]:

-2- For templates

less,greater,less_equal, andgreater_equal, […], if the call operator calls a built-in operator comparing pointers, the call operator yields a strict total order that is consistent among those specializations and is also consistent with the partial order imposed by those built-in operators. Relational comparisons of pointer values are not required to be usable as constant expressions.

*[2021-04-05; Jiang An comments and provides alternative wording]*

The libc++ and MSVC STL implementations only support flat address spaces, and always use comparison operators.
The libstdc++ implementation casts pointer values to `uintptr_t` if the direct comparison result is unusable
in constant evaluation.

So, I think that we can specify that the implementation-defined strict total order (3.26 [defns.order.ptr]) generates a core constant expression if and only if the corresponding underlying comparison expression comparing pointer values is a core constant expression. No any other case should be a core constant expression, otherwise we should also make the underlying comparison expression a core constant expression.

IMO the proposed resolution is already implemented in libc++, libstdc++, and MSVC STL, and implementable on compilers
that either support flat address spaces only or have implemented intrinsics needed for transparent comparison operators
and `std::is_constant_evaluated`.

**Proposed resolution:**

This wording is relative to N4885.

Add at the end of 22.10.8 [comparisons] p2:

-2- For templates

`less`,`greater`,`less_equal`, and`greater_equal`, the specializations for any pointer type yield a result consistent with the implementation-defined strict total order over pointers (3.26 [defns.order.ptr]). [*Note 1*: If`a < b`is well-defined for pointers`a`and`b`of type`P`, then`(a < b) == less<P>()(a, b)`,`(a > b) == greater<P>()(a, b)`, and so forth. —*end note*] For template specializations`less<void>`,`greater<void>`,`less_equal<void>`, and`greater_equal<void>`, if the call operator calls a built-in operator comparing pointers, the call operator yields a result consistent with the implementation-defined strict total order over pointers. A comparison result of pointer values is a core constant expression if and only if the corresponding built-in comparison expression is a core constant expression.Add at the end of 22.10.9 [range.cmp] (3.1):

-3-

*Effects:*(3.1) — If the expression

`std::forward<T>(t) == std::forward<U>(u)`results in a call to a built-in operator`==`comparing pointers: returns`false`if either (the converted value of)`t`precedes`u`or`u`precedes`t`in the implementation-defined strict total order over pointers (3.26 [defns.order.ptr]) and otherwise`true`. The result is a core constant expression if and only if`std::forward<T>(t) == std::forward<U>(u)`is a core constant expression.(3.2) — Otherwise, equivalent to:

`return std::forward<T>(t) == std::forward<U>(u);`

Add at the end of 22.10.9 [range.cmp] (7.1):

-7-

*Effects:*(7.1) — If the expression

`std::forward<T>(t) < std::forward<U>(u)`results in a call to a built-in operator`<`comparing pointers: returns`true`if (the converted value of)`t`precedes`u`in the implementation-defined strict total order over pointers (3.26 [defns.order.ptr]) and otherwise`false`. The result is a core constant expression if and only if`std::forward<T>(t) < std::forward<U>(u)`is a core constant expression.(7.2) — Otherwise, equivalent to:

`return std::forward<T>(t) < std::forward<U>(u);`

Add at the end of 22.10.8.8 [comparisons.three.way] (3.1):

-3-

*Effects:*(3.1) — If the expression

`std::forward<T>(t) <=> std::forward<U>(u)`results in a call to a built-in operator`<=>`comparing pointers: returns`strong_ordering::less`if (the converted value of)`t`precedes`u`in the implementation-defined strict total order over pointers (3.26 [defns.order.ptr]),`strong_ordering::greater`if`u`precedes`t`, and otherwise`strong_ordering::equal`. The result is a core constant expression if and only if`std::forward<T>(t) <=> std::forward<U>(u)`is a core constant expression.(3.2) — Otherwise, equivalent to:

`return std::forward<T>(t) <=> std::forward<U>(u);`