通过使用 constexpr 参数调用 constexpr 函数来初始化静态存储变量

问题描述 投票:0回答:1

我有以下无限递归

constexpr
函数:

constexpr int foo(int x) {
    return foo(x + 1);
}

然后我发现

int main() {
    static int x = foo(5);
    static int y = std::integral_constant<int, foo(5)>::value;
    static constinit int z = foo(5);
}

由于编译时无限递归,编译器(GCC13,在 Ubuntu 22.04 上)报告

y
z
的初始化错误,但
x
没有错误。如果我们删除
y
z
的声明然后运行程序,就会导致地址边界错误。

这里

5
是一个常量表达式,但是
x
foo(5)
的初始化不是在编译时执行的。为什么?

更重要的是,我尝试修改

foo

constexpr int foo(int x) {
    if (std::is_constant_evaluated())
        return 42;
    return foo(x + 1);
}

然后我发现

x
被初始化为42,没有编译时或运行时无限递归,说明这次是在编译时发生的。

那么,

foo(5)
的初始化器
x
是否在编译时评估?这个初始化是静态初始化还是动态初始化?实际规则是什么?

而且,我也不太熟悉

constinit
。使用
y
初始化
std::integral_constant
的原因是我们要确保静态初始化。我可以说写作
constinit
始终是一种安全且现代的替代方案吗?

c++ constexpr static-initialization constinit
1个回答
0
投票

当且仅当

static int
变量
x
的初始值设定项 (
foo(5)
) 是常量表达式时,它才会被常量初始化。如果不是常量初始化,则进行动态初始化。

[expr.const]/2,强调我的:

变量或临时对象 o 是 常量初始化 if

  • 它有一个初始化程序或其默认初始化会导致执行一些初始化,并且
  • 当解释为常量表达式时,其初始化的完整表达式是常量表达式,但如果 o 是一个对象,则该完整表达式也可以调用 o 及其子对象的 constexpr 构造函数,即使这些对象是非文字类类型。
    [注2: 这样的类可以有一个不平凡的析构函数。在此评估中,std::is_constant_evaluated() ([meta.const.eval]) 返回 true。 — 尾注]

[基本.开始.静态]/2:

如果具有静态或线程存储持续时间的变量或临时对象被常量初始化([expr.const]),则执行

常量初始化。 [...] 零初始化和常量初始化一起称为“静态初始化”;所有其他初始化都是动态初始化

并且
constinit

是确保静态初始化的正确方法。

[dcl.constinit]/2

:

如果使用
constinit

说明符声明的变量具有动态初始化 ([basic.start.dynamic]),则程序的格式不正确,即使实现将该初始化作为静态初始化执行 ([basic.start.static] ]).


    

© www.soinside.com 2019 - 2024. All rights reserved.