std::less 是否应该允许在编译时比较不相关的指针?

Is std::less supposed to allow comparison of unrelated pointers at compile-time?

考虑这段代码:

#include <functional>
#include <typeinfo>

template <typename T>
inline constexpr const void *foo = &typeid(T);

int main()
{
    constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);
} 

Run on gcc.gotbolt.org

如果我在这里使用 < 而不是 std::less,代码将无法编译。这并不奇怪,因为如果指针指向不相关的对象,关系指针比较的结果是 unspecified,显然这样的比较不能在编译时完成。

<source>:9:20: error: constexpr variable 'a' must be initialized by a constant expression
    constexpr bool a = foo<int> < foo<float>;
                   ^   ~~~~~~~~~~~~~~~~~~~~~
<source>:9:33: note: comparison has unspecified value
    constexpr bool a = foo<int> < foo<float>;
                                ^

代码仍然无法编译,即使我使用 std::less。编译器错误是一样的。 std::less 似乎至少在 libstdc++ 和 libc++ 中实现为 <;我在 GCC、Clang 和 MSVC 上得到了相同的结果。

但是,关于 std::less 的 cppreference 页面声称:

  1. 它的operator()constexpr

  2. 它神奇地实现了指针的严格全序,即可以用来比较不相关的指针和合理的结果。

所以,这是所有这些编译器中的一个错误,还是我遗漏了有关 std::less 的一些细节,导致上面的代码格式错误?

要成为有效的 constexpr 函数,它应该具有结果为 constexpr 的参数,不需要所有参数。

例如

constexpr int foo(bool b) { if (!b) throw 42; return 42; }

有效,f(true)可以在constexpr中使用(即使f(false)不能)。

constexpr int a[2]{};
constexpr bool b = std::less<const void*>{}(&a[0], &a[1]);

有效且足以让 less::operator() 成为 constexpr

我认为没有指定 ranges/values 对于标准中的 constexpr 是正确的。

所以所有的编译器都是正确的。

我认为您提出的问题没有明确的答案。这是 LWG 2833 的一个特例:标记一个库函数 constexpr 并不能解释在什么情况下调用该函数会产生常量表达式。

在解决此问题之前,我认为您不能指望 std::less 能够在编译时比较不相关的指针。

在你的问题中,你声明了一个像constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);这样的变量,constexpr变量需要满足以下规则:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression. (Note the emphasized part)

常量表达式必须是核心常量表达式,因此它必须满足核心常量表达式规则:

a relational or equality operator where the result is unspecified;

在您的代码中,&typeid(int)&typeid(float) 未指定,原因是:

Comparing unequal pointers to objects is defined as follows:

  • If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript compares greater.
  • If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control and provided their class is not a union.
  • Otherwise, neither pointer compares greater than the other.

&typeid(int)&typeid(float) 符合第三个要点。并且

If two operands p and q compare equal, p<=q and p>=q both yield true and pq both yield false. Otherwise, if a pointer p compares greater than a pointer q, p>=q, p>q, q<=p, and q<p all yield true and p<=q, p<q, q>=p, and q>p all yield false. Otherwise, the result of each of the operators is unspecified.

所以,比较&typeid(int)&typeid(float)的结果是不确定的,因此它不是一个常量表达式。