一个可变 constexpr 变量可以在 C++ 中初始化另一个变量吗?

Can one volatile constexpr variable initialize another one in C++?

提问人:Fedor 提问时间:11/21/2022 更新时间:11/24/2022 访问量:1491

问:

C++ 标准允许每个缺陷报告 1688 的变量,该问题已于 2013 年 9 月解决:constexpr volatile

该组合是有意允许的,在某些情况下可用于强制恒定初始化。

看起来意图是只允许 ,这在 C++20 之前是不可用的。constinit volatile

尽管如此,当前的编译器在某些情况下的处理方式仍存在分歧。例如,此程序通过另一个变量初始化一个这样的变量:constexpr volatile

int main() {
    constexpr volatile int i = 0;
    constexpr volatile int j = i;
    return j;
}

它在 GCC 和 MSVC 中被接受,但 Clang 抱怨:

error: constexpr variable 'j' must be initialized by a constant expression
    constexpr volatile int j = i;
                           ^   ~
note: read of volatile-qualified type 'const volatile int' is not allowed in a constant expression
    constexpr volatile int j = i;

在线演示:https://gcc.godbolt.org/z/43ee65Peq

哪个编译器就在这里,为什么?

C++ 语言-lawyer constexpr volatile

评论

0赞 Shafik Yaghmour 11/23/2022
这看起来非常接近:stackoverflow.com/a/28885047/1708801 我解释的地方,它在常量表达式中不可用。

答:

12赞 Maciej Polański 11/21/2022 #1

您链接的缺陷报告显示它应该不起作用,因此 Clang 是正确的。

(...)允许使用Constexpr定义的非易失性对象“(...),但此类变量不能出现在常量表达式中。意图是什么?

但更有趣的是:为什么 Clang 关心而其他编译器却不在乎?

在我看来,发生这种情况是因为 JF Bastien,一个在 Clang / LLVM 世界中非常有影响力的人物,他个人不喜欢:)volatile

长期以来,他一直提议将其从语言中删除。因此,如果允许在某个地方禁止挥发性,他可能会不遗余力地做到这一点。如果没有其他原因,只是为了阻止人们编写代码,如果他的提议最终被接受,这些代码将不得不重写。

他还在 CppCon 上发表了关于他的弃用提案的演讲,如果你想知道他的理由。

评论

1赞 Peter Cordes 11/21/2022
就标准应该说什么以及编译应该如何行为而言,读取任何对象的值都可以是一个常量表达式是没有意义的。强制在运行时从某个位置实际读取值的用例之一是,使用调试器更改值等恶作剧是可能的。也许实际上并没有更改 constexpr 的值,但可以保证读取将在运行时发生,而不是在编译时发生,这排除了成为常量表达式的一部分的可能性。volatilevolatile
0赞 Peter Cordes 11/21/2022
如果 JF Bastien 参与了决定 clang 如何处理这种情况,那么它似乎只不过是强制执行我所期望的工作方式:它可以存在于该部分中,并且不像要求它的初始值设定项是一个常量表达式。但是不允许它进行常量传播,因此它不能成为其他常量表达式的一部分。在这种情况下,阅读易失性不应该起作用,而不是发明理由来“禁止”它的情况。constexpr volatile.rodataconst
3赞 Peter Cordes 11/21/2022
JF Bastien 演讲的 TL:DR 是他想可变访问替换类型限定符,并作为执行易失性访问的方法。对我来说是有道理的。(作为限制这些可以适用的内容的一种方式,只适用于在 asm 中真正有意义的事情。volatilevolatile_loadvolatile_store
0赞 supercat 11/22/2022
@PeterCordes:我认为这种组合更有用的意义是强制编译器将数据的静态初始化视为可观察的,并说这优先于读取值的时间。例如,如果标准规定允许编译器合并地址,但如果任何一个对象是“易失性的”,则不允许这种合并,这也可能是有用的。constexprvolatileconstexpr int x=123; constexpr int y=123;
17赞 Brian Bi 11/21/2022 #2

Clang 是正确的。from 的初始化要求对 执行左值到右值的转换,但根据 [expr.const]/5.9,在常量表达式中绝不允许对 glvalue 进行左值到右值的转换。由于是一个变量,因此它必须由常量表达式初始化。jiivolatileiconstexpr

我不知道为什么 GCC 和 MSVC 选择不执行此规则,除了所有 C++ 编译器都永远人手不足,无法实现他们期望的一切。

评论

0赞 supercat 11/22/2022
一般来说,根据我的理解,给定 ,限定符意味着编译器可以选择将读取请求替换为任何其他会产生 .如果限定词在何时被限定时不邀请这种替换,那么该限定词会产生什么影响?如果没有 ,如果唯一使用的函数还初始化了静态持续时间的其他对象,则编译器让函数的开头同时初始化 和 可能是有意义的,从而避免......constexpr int x=someConstExpr;constexprxsomeConstExprconstexprxvolatile,volatilexyxy
0赞 supercat 11/22/2022
...需要对 进行一些单独的静态初始化。然而,一个合格者会告诉编译器,它必须假设在静态初始化完成后发生的写入在某种程度上是可观察的,因此避免执行任何操作。xvolatilex
1赞 Brian Bi 11/22/2022
@supercat 正如缺陷报告上的注释所示,和的组合可用于声明变量,同时强制常量初始化。这是一个奇怪的现象,不太可能特别有用,但它的行为直接遵循语言规则。constexprvolatilevolatile
0赞 supercat 11/23/2022
这与 ?如果优先考虑,这将适应需要强制链接器包含似乎未在程序中使用的定义的情况(例如,允许外部实用程序检查可执行文件并提取常量的值)。该标准不会考虑外部程序可能提取此类信息的方法,但这并不意味着不应该有防止链接器省略它的标准方法。constexpr int two = 2; volatile int volatile_two = two;constexpr