constexpr(即常量初始化)模板变量的初始化顺序是否有保证?

8
en.cppreference.com/w/cpp/language/initialization 来看:
仅针对未明确特化的(静态/线程本地)类模板静态数据成员和变量模板(自 C++14 起),无序动态初始化。因此,静态模板似乎容易受到更糟糕的 The Static Initialization Order Fiasco (TSIOF)(静态初始化顺序混乱) 的影响(即在一个翻译单元内无序)。
使用 constexpr 可以消除这种漏洞吗?
即下面代码的输出是否保证success
显然,由于这个问题的性质,工作示例不足以作为答案;必须引用标准中的语句。(优先考虑 C++17 的答案)
#include<cassert>

template<class T> static constexpr T a = 41;
template<class T> static constexpr T b = a<T>+1;
int main(){
    assert(b<int> == 42);
    std::cout <<"success\n";
}

顺便提一下,如果有专家在这方面,请回答我相关的未解决问题(对于这样的专家来说,这应该很容易回答)这里。 另外,如果我的其他问题的答案是否定的(即constexpr不能跨翻译单元帮助),会有什么影响?

更新:我需要澄清这里的关注点。原始问题标题问是否初始化顺序对constexpr模板变量具有影响。我已经澄清了。我不担心示例中是否发生动态初始化;它没有。我的担忧是,在动态初始化情况下无法假定有序初始化,那么在常量初始化情况下可以吗?在看到相同翻译单元内的动态初始化模板变量的行为之前,我甚至从未想过这个问题。然而,由于静态持续时间的动态初始化模板变量并不提供有序初始化,因此我现在认为也没有理由假设具有保证顺序初始化的静态持续时间的常量初始化模板变量。我需要百分之百确定模板变量的常量初始化在翻译单元内按照其定义的顺序进行。

再次强调,如果动态初始化程序没有按顺序初始化,我认为无法假定编译器内部的常量初始化程序需要按顺序初始化。标准中没有警告说明常量初始化是无序的不足以说明问题。

我意识到某些人可能认为这是过度关注,但我正在开发安全关键的软件,而我的公司已经将采用C++14暂停,直到解决此问题。


该引用明确指出初始化是无序的,即不被保证。 - Lotney
2
你甚至可以通过使用 static_assert 来更改你的 assert - Jarod42
该引用明确说明初始化是无序的,即不被保证。我需要标准中明确给出这一保证的引用。主要问题在于没有概念证明可以回答这个问题。受TSIOF影响的代码可能会在产生真正可怕的错误之前工作多年。在我看来,这是C++最严重的问题。在模板变量之前,我一直在使用相同翻译单元的解决方案。我不知道模板变量存在这个问题。幸运的是,在发布之前我发现了代码中的错误。 - Lotney
别误会,我99%确定constexpr在这里可以工作,但是考虑到模板变量的行为,我需要修订编码标准并使用标准语言。有人可能会认为static_assert只能证明它在测试用例中可以工作。 - Lotney
1
constexpr 不能进行动态初始化,这是其定义所决定的。 - Passer By
显示剩余5条评论
2个回答

3

根据basic.start.static

常量初始化是指当具有静态或线程存储期的变量或临时对象使用常量初始化器进行初始化时所执行的操作。

在您的代码中:

template<class T> static constexpr T a = 41; // constant initialization

正在进行常量初始化,这将使得:

template<class T> static constexpr T b = a<T>+1;

由于模板的常量求值,可以用42初始化。

这是根据以下规定(来自expr.const/8.7):

一个变量如果作为潜在的常量表达式出现,那么它要么是constexpr变量,要么是非易失性const限定的整数类型或引用类型。

因此,保证输出总是"success"

注意(来自basic.start.static/2):

零初始化常量初始化一起被称为静态初始化

--而不是动态初始化。


谢谢您的回答。我已经更新了我的问题,以澄清我所寻找的内容。 - Lotney

0

[请注意,在本文中,a<T>b<T>分别缩写为ab]。

[expr.const/3]指出:

如果一个变量是constexpr变量,或者它是引用类型或const限定的整数或枚举类型,并且它的初始化器是常量初始化器,则在其初始化声明遇到后,该变量可以在常量表达式中使用。

因此,由于constexpr说明符,a将在b的初始化期间可用。正如[dcl.constexpr/1]的第一句话所述,constexpr完全适用于变量和变量模板。

constexpr说明符仅适用于变量或变量模板的定义,或函数或函数模板的声明。

因此,声明为constexpr的变量或变量模板可在其自身初始化后的常量表达式中使用(包括其他constexpr变量的初始化),其值由其初始化中指定(由于constexpr隐式保证了常量初始化)。

如果我们希望探索[expr.const/3]中的第二个选项,而不是依赖于“constexpr意味着是”的条款,那么这也将导致在更迂回的路线中使用ab的初始化期间,这需要深入多个标准部分并组合隐含的保证。

首先,会出现一个问题,即a是否具有常量初始化器。为了确定它是否具有常量初始化器,我们可以参考[expr.const/2],其中写道(注意省略):

变量或临时对象o的常量初始化器是指其初始化器被解释为常量表达式的完整表达式结果为常量表达式,但如果o是对象,则这样的初始化器也可以调用o及其子对象的constexpr构造函数,即使这些对象是非文字类类型。

我们有两个明确的保证,即初始化器的完整表达式是常量表达式,均来自于 [dcl.constexpr/9]a 隐式地为 const,而不将完整表达式作为常量表达式将会引发错误。

在对象声明中使用 constexpr 指定符会将该对象声明为常量。该对象应具有字面类型并且应进行初始化。 任何 constexpr 变量声明中,初始化的完整表达式都应为常量表达式。

这也意味着(截至 C++14),a 不会受到零初始化的影响([basic.start.static/2],省略无关部分):

如果变量或具有静态或线程存储持续时间的临时对象使用常量初始化程序([expr.const])进行初始化,则执行常量初始化。 如果未执行常量初始化,则具有静态存储持续时间([basic.stc.static])或线程存储持续时间([basic.stc.thread])的变量将被零初始化([dcl.init])。
为了验证在初始化后a是否具有正确的值,如果我们想要真正地仔细检查,我们可以看一下[intro.execution/9]
每个完整表达式相关的值计算和副作用都在求值的下一个完整表达式相关的值计算和副作用之前排序。

我们知道a的初始化是一个完整表达式,因此我们可以隐含地保证在其完整表达式结束之前(包括其他初始化)顺序中的下一个完整表达式被评估之前,a将被分配值41。通过结合[expr.const/3][basic.start.static/2],我们因此保证了(与constexpr子句一样),a可用于其自身初始化后遇到的常量表达式,例如b的初始化,并且保证a == 41 && a != 0


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接