假设我这样初始化变量:
#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]
由于整数溢出,b
和c
都产生未定义行为。
对于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);
也可以。
从技术上讲,您的问题中的consteval
lambda的解决方案是病态的,不需要诊断,因为lambda标记为consteval
,但在调用时永远不会产生常量表达式。但是我希望任何非恶意编译器仍然可以诊断出这样的调用。