为什么constinit允许UB?



假设我这样初始化变量:

#include <cstdint>
constexpr uint16_t a = 65535;
constinit int64_t b = a * a; // warning: integer overflow in expression of type 'int' results in '-131071' [-Woverflow]
constexpr int64_t c = a * a; // error: overflow in constant expression [-fpermissive]

由于整数溢出,bc都产生未定义行为。

对于constinit,变量是常数初始化。这对UB没有任何保证。

对于constexpr,变量是用常量表达式初始化的。常量表达式保证没有任何UB。这里有符号整数溢出导致错误。但是该变量也自动为const。

那么,如何用常量表达式初始化非const变量呢?我一定要写

constexpr int64_t t = a * a; // error: overflow in constant expression [-fpermissive]
constinit int64_t b = t;

constinit int64_t b = []()consteval{ return a * a; }(); // error: overflow in constant expression

每次?

这与CWG问题2543有关。

目前,因为编译器允许用静态初始化替换任何动态初始化,并且因为constinit只被指定为强制"无动态初始化",所以它可能仍然允许非常量表达式的初始化(可能取决于链接问题中讨论的解释)。因此,constinit反映了是否会在运行时进行初始化(这与避免动态初始化顺序问题有关)。它不一定反映初始化式是否为常量表达式。

正如问题描述中所述,这实际上是不可实现的,因为动态/静态初始化的选择在编译过程中太晚了,不能总是使constinit正确地反映它。

对于该问题的一种可能的解决方案,可以将constinit的规范更改为实际要求对变量进行常量初始化,而不是仅仅要求不进行动态初始化。如果采用了这种解决方案,那么b初始化的第一个示例也将要求编译器诊断UB,并且所有其他解决方案都将过时。

问题描述似乎并没有真正偏向任何方向。


对于当前的情况(如果解决方案采取了另一个方向),您给出的解决方案的替代方案是:

template<typename T>
consteval auto force_compiletime(T&& t) {
return std::forward<T>(t);
}

template<typename To, typename T>
consteval To force_compiletime2(T&& t) {
return std::forward<T>(t);
}

constinit auto t = force_compiletime(static_cast<int64_t>(a * a));

constinit auto t = force_compiletime2<int64_t>(a * a);

注意,您需要在初始化器中以这种方式包含目标类型,否则将无法诊断转换中任何潜在的UB。如果你不关心

constinit int64_t t = force_compiletime(a * a);

也可以。


从技术上讲,您的问题中的constevallambda的解决方案是病态的,不需要诊断,因为lambda标记为consteval,但在调用时永远不会产生常量表达式。但是我希望任何非恶意编译器仍然可以诊断出这样的调用。

相关内容

  • 没有找到相关文章

最新更新