繁体   English   中英

为什么 constinit 允许 UB?

[英]Why does constinit allow 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。

那么如何最好地使用常量表达式初始化非常量变量?

我必须写吗

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但在调用时永远不会产生常量表达式。 但我希望任何非恶意编译器仍能诊断出这样的调用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM