比较两个 constexpr 指针不是 constexpr 吗?

Rum*_*rak 5 c++ pointers constexpr

我正在寻找一种在编译时将类型映射到数值的方法,理想情况下不使用答案中建议的散列。

由于指针可以constexpr,我试过这个:

struct Base{};
template<typename T> struct instance : public Base{};

template<typename T>
constexpr auto type_instance = instance<T>{};

template<typename T>
constexpr const Base* type_pointer = &type_instance<T>;

constexpr auto x = type_pointer<int> - type_pointer<float>; // not a constant expression
Run Code Online (Sandbox Code Playgroud)

gcc 和 clang 都拒绝此代码,因为type_pointer<int> - type_pointer<float>它不是一个常量表达式,例如,请参见此处

为什么?

我可以理解,从一个编译到下一个编译,这两个值之间的差异不会稳定,但在一个编译中,它应该是constexpr,恕我直言。

wal*_*nut 11

两个不指向同一个数组或同一个对象(包括一个过去的数组/对象)的非空指针相减是未定义的行为,参见[expr.add](特别是第 5 和第 7 段) C++17 标准(最终草案)。

如果求值,则具有核心未定义行为[1] 的表达式永远不会是常量表达式,请参阅[expr.const]/2.6

因此type_pointer<int> - type_pointer<float>不能是一个常量表达式,因为两个指针都指向不相关的对象。

由于type_pointer<int> - type_pointer<float>不是常量表达式,因此不能用于初始化constexpr变量,例如

constexpr auto x = type_pointer<int> - type_pointer<float>;
Run Code Online (Sandbox Code Playgroud)

尝试使用非常量表达式作为constexpr变量的初始值设定项会使程序格式错误并要求编译器打印诊断消息。这就是您看到的错误消息。

基本上,当核心未定义行为出现在纯编译时上下文中时,编译器需要对其进行诊断。

您可以看到如果指针指向同一个对象,则不会出现错误,例如:

constexpr auto x = type_pointer<int> - type_pointer<int>;
Run Code Online (Sandbox Code Playgroud)

这里的减法是明确定义的,并且初始化器是一个常量表达式。所以代码将被编译(并且不会有未定义的行为)。x将有一个明确定义的值0


请注意,如果您使用xnon-constexpr编译器将不再需要诊断未定义的行为并打印诊断消息。因此很可能会编译。

减去不相关的指针仍然是未定义的行为,而不仅仅是未指定的行为。因此,您将失去对结果程序将做什么的任何保证。这不仅意味着您将x在每次编译/执行代码时获得不同的值。


[1]这里的核心未定义行为是指核心语言中的未定义行为,与使用标准库导致的未定义行为相对。它是未指定的,作为该库指定不确定的行为是否导致(否则常数)表达为不是恒定的表达,参见最后一句中的示例之前[expr.const] / 2