std::less<T*>
in constant expressionSection: 22.10.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.
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.
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
, andgreater_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: Ifa < b
is well-defined for pointersa
andb
of typeP
, then(a < b) == less<P>()(a, b)
,(a > b) == greater<P>()(a, b)
, and so forth. — end note] For template specializationsless<void>
,greater<void>
,less_equal<void>
, andgreater_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: returnsfalse
if either (the converted value of)t
precedesu
oru
precedest
in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]) and otherwisetrue
. The result is a core constant expression if and only ifstd::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: returnstrue
if (the converted value of)t
precedesu
in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]) and otherwisefalse
. The result is a core constant expression if and only ifstd::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: returnsstrong_ordering::less
if (the converted value of)t
precedesu
in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]),strong_ordering::greater
ifu
precedest
, and otherwisestrong_ordering::equal
. The result is a core constant expression if and only ifstd::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);