为什么在 gcc 中动态初始化发生在静态初始化之前

why dynamic initialization occur before static initialization in gcc

#include <iostream>
struct NonConstant{
    NonConstant(int v):v_(v){
        std::cout<<"NonConstant\n";
    }
    int v_;
};

struct Constant{
    constexpr Constant(int v):v_(v){
        if(v_==0){
         std::cout<<"Constant\n";
        }
    }
    int v_;
};

NonConstant a = 2; //#1
Constant b = 0;   //#2

int main(){
}

outcome 将是:

NonConstant
Constant

我对这个结果感到困惑,因为,根据标准规则,#1 不是静态初始化,#2 是,因为这些:

A constant initializer for a variable or temporary object o is an initializer whose full-expression is a constant expression, except that if o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
Constant initialization is performed if a variable or temporary object with static or thread storage duration is initialized by a constant initializer for the entity.If constant initialization is not performed, a variable with static storage duration or thread storage duration is zero-initialized. Together, zero-initialization and constant initialization are called static initialization;all other initialization is dynamic initialization. All static initialization strongly happens before ([intro.races]) any dynamic initialization.

classNonConstant的构造函数未由constexpr指定,NonConstant a = 2;的初始化将调用对象a的非constexpr构造函数,因此, #1 的初始化不是静态初始化,所以它是动态初始化。相比之下,Constant b = 0; 的初始化是静态初始化,因为调用的构造函数是 constexpr 构造函数。并且规则说 所有静态初始化强烈发生在任何动态初始化之前 。那么,为什么结果暗示 #1 的评估发生在 #2 的评估之前?如果我遗漏了什么,请纠正我。

更新:

在这个问题后面的评论中,有人说除了构造函数的class可以是非字面量类型外,constexpr构造函数在任何方面都必须是有效的核心常量表达式,即调用 std::cout 将使 constexpr 构造函数不是核心常量表达式。然而,我在cppreference中找到了另一种解释,即:

Constant initialization is performed after (until C++14)instead of (since C++14) zero initialization of the static and thread-local objects and before all other initialization. Only the following variables are constant initialized:

  1. [...]
  2. Static or thread-local object of class type that is initialized by a constructor call, if the constructor is constexpr and all constructor arguments (including implicit conversions) are constant expressions, and if the initializers in the constructor's initializer list and the brace-or-equal initializers of the class members only contain constant expressions.

并没有说constexpr构造函数必须是核心常量表达式。只要被调用的构造函数满足它由 constexpr 限定并且它的参数都必须是常量表达式并且成员初始化器必须是常量表达式。所以,#2 确实是一个常量初始化,因为参数 0 是一个常量表达式,并且选择的构造函数由说明符 constexpr 限定,成员初始化器遵守 [=30] 中提到的这些规则=].

b有动态初始化,没有静态初始化。

正如您对 [basic.start.static]/2 的引用所解释的那样,b 只有在其初始化程序的完整表达式(即 Constant(int) 构造函数的执行)是的情况下才具有静态初始化常量表达式。

[expr.const]/2中,我们读到:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • ...

  • an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor ([class.dtor]) [ Note: Overload resolution is applied as usual  —  end note ] ;

  • ...

这里“遵循抽象机规则”的构造函数求值包括构造函数体。由于初始化程序是 0,该评估将调用 std::operator<<(std::ostream&, const char*),而不是 constexpr。所以初始化器的完整表达式不是核心常量表达式,也不是常量表达式。

当然,虽然这不是严格的技术定义,但“常量表达式”的全部要点是定义我们何时保证编译器可以在编译时处理某些东西。并且写入程序的标准输出肯定不会在编译时发生。

cppreference.com 是一个尽可能准确的好资源,但它不能替代实际标准的权威。关于使用 class 构造函数进行常量初始化的引用对于 C++14 和 C++17 是不正确的。我怀疑它实际上是 C++11 遗留下来的,其中 constexpr 构造函数的主体根本不允许评估任何函数调用,并且 [expr.const] 类似地描述了对 [ 的使用要求=17=] 根据成员初始值设定项在核心常量表达式中构造函数。