This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 2491. `std::less<T*>` in constant expression

Section: 20.14.8 [comparisons] Status: New Submitter: Agustín K-ballo Bergé Opened: 2015-04-01 Last modified: 2021-04-10

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.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4618.

> 1. Add at the end of 20.14.8 [comparisons]:

>    > -2- For templates `less`, `greater`, `less_equal`, and `greater_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.27 [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.

1. Add at the end of 20.14.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.27 [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.

2. Add at the end of 20.14.9 [range.cmp] (3.1):

   > -3- Effects:

   > 1. (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.27 [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.

   > 2. (3.2) — Otherwise, equivalent to: `return std::forward<T>(t) == std::forward<U>(u);`

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

   > -7- Effects:

   > 1. (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.27 [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.

   > 2. (7.2) — Otherwise, equivalent to: `return std::forward<T>(t) < std::forward<U>(u);`

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

   > -3- Effects:

   > 1. (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.27 [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.

   > 2. (3.2) — Otherwise, equivalent to: `return std::forward<T>(t) <=> std::forward<U>(u);`