使用 constinit 变量初始化 constexpr 变量

Using constinit variable to initialize a constexpr variable

本文关键字:变量 constexpr 初始化 使用 constinit      更新时间:2023-10-16

看看这个小例子:

constinit int a = 0;
constexpr int b = a;

Clang没有编译它(Godbolt(:

2:15:错误:constexpr 变量"b"必须由常量表达式初始化

这是正确的诊断吗?

如果是,为什么标准不允许这样做?我知道,a的值可能会在运行期间(甚至在动态初始化期间(发生变化,但在常量初始化时,它的值是已知的,因此可用于初始化b

是的,诊断是正确的。constexpr变量必须使用常量表达式初始化,并且a不是常量表达式(它是可变变量(。

constinit(P1143( 的目的是在变量声明的初始化不是恒定的情况下强制变量声明格式不正确。它不会更改变量本身的任何内容,例如它是类型或任何内容(以隐式const的方式constexpr(。愚蠢的例子:

struct T {
int i;
constexpr T(int i) : i(i) { }
T(char c) : i(c) { }
};
constinit T c(42); // ok
constinit T d('X'); // ill-formed

这就是constinit的全部目的,唯一真正的规则是[dcl.constinit]/2:

如果使用constinit说明符声明的变量具有动态初始化([basic.start.dynamic](,则程序格式不正确。 [注意:constinit说明符确保在静态初始化期间初始化变量([basic.start.static](。 —尾注]

constinit中的 const 仅指初始化,不指变量,也不指任何类型的。请注意,它也不会更改执行的初始化类型,它只是诊断是否执行了错误的初始化类型。

在:

constinit int a = 0;
constexpr int b = a;

0是一个常量表达式,因此a的初始化格式正确。一旦我们克服了这一点,说明符就不做任何事情了。它相当于:

int a = 0; // same behavior, a undergoes constant initialization
constexpr int b = a;

这完全是格式错误的。


但是在常量初始化时,它的值是已知的,因此可用于初始化b

当然,此时此刻。怎么样:

constinit int a = 0;
cin >> a;
constexpr int b = a;

这显然不会飞。允许这一点需要扩展常量表达式是什么(在我看来,已经是标准中最复杂的规则(以允许非常量变量,但仅在初始化后立即?复杂性似乎不值得,因为您可以随时编写:

constexpr int initializer = 0;
constinit int a = initializer;
constexpr int b = initializer;

constexpr无一例外地结合了constinitconst

constinit强制使用编译时常量表达式进行初始化,并在静态初始化期间强制初始化,不允许动态初始化。不过,它不会以任何方式更改变量本身。

const禁止更改变量,但可以通过mutable成员来削弱变量。

两者共同使其成为编译时常量表达式。

总之,是的,诊断是正确的。

这是正确的诊断吗?

我会说是的。根据 cpp 偏好:

constinit - 指定变量必须具有静态初始化, 即零初始化和常量初始化,否则 程序格式不正确。

静态(常量(初始化和常量表达式是不同的概念,因为常量表达式可以在常量初始化中使用,但不能以相反的方式使用。constinit不应该与const混淆。这意味着初始化(仅(是恒定的。

但是,constinit const可以在constexpr中使用,并且它们应该是相同的。

反例:

constinit int a = 0;
struct X{
X() {
a = 4;
}
};
X x{};
constexpr int b = a;

b应该是什么? 关键是,在看到b之前,a可以以非常量的方式进行更改。