【发布时间】:2020-05-29 05:51:59
【问题描述】:
#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 是,因为这些:
一个变量或临时对象的常量初始化器 o 是一个其完整表达式是一个常量表达式的初始化器,除了如果 o 是一个对象,这样的初始化器还可以为 o 调用 constexpr 构造函数及其子对象,即使这些对象属于非文字类类型。
如果具有静态或线程存储持续时间的变量或临时对象由实体的常量初始化程序初始化,则执行常量初始化。如果不执行常量初始化,则将具有静态存储持续时间或线程存储持续时间的变量初始化为零。 零初始化和常量初始化统称为静态初始化;其他所有初始化都是动态初始化。 所有静态初始化都发生在 ([intro.races]) 任何动态初始化之前。
类NonConstant的构造函数不是由constexpr指定的,NonConstant a = 2;的初始化会为对象a调用一个非constexpr构造函数,因此#1的初始化不是静态初始化,所以是动态初始化。相比之下,Constant b = 0; 的初始化是静态初始化,因为被调用的构造函数是 constexpr 构造函数。并且规则说所有静态初始化都强烈发生在任何动态初始化之前。那么,为什么结果意味着#1 的评估发生在#2 的评估之前?如果我错过了什么,请纠正我。
更新:
在本题后面的cmets中,有人说除了构造函数的类可以是非文字类型外,constexpr构造函数在任何方面都必须是有效的核心常量表达式,即std::cout的调用会使 constexpr 构造函数不是核心常量表达式。不过,我在cppreference找到了另一种解释,那就是:
在(C++14 之前)而不是(C++14 起)静态和线程局部对象的零初始化之后以及所有其他初始化之前执行常量初始化。只有以下变量被常量初始化:
- [...]
- 由构造函数调用初始化的类类型的静态或线程本地对象,如果构造函数是 constexpr 并且所有构造函数参数(包括隐式转换)都是常量表达式,并且如果构造函数的初始值设定项列表中的初始值设定项并且类成员的大括号或等号初始化器只包含常量表达式。
并不是说 constexpr 构造函数必须是核心常量表达式。只要调用的构造函数满足constexpr 限定并且它的参数都必须是常量表达式并且成员初始化器必须是常量表达式。所以,#2 确实是一个常量初始化,因为参数0 是一个常量表达式,并且被指定符constexpr 限定的选定构造函数和成员初始化器遵循expr.const 中提到的这些规则。
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: c++ c++17 language-lawyer