C++20数组类型复制初始化合法性及constexpr影响问题
先明确场景:我们讨论的是using t = int[1];(即t是大小为1的int数组类型)下的各种初始化情况,示例代码如下:
static_assert([]{ using t = int[1]; constexpr t a{}; // 所有编译器均通过 constexpr t a = {}; // 所有编译器均通过 constexpr t a = t{}; // Clang报错,GCC、MSVC通过 t a = t{}; // Clang、MSVC报错,GCC通过 constexpr auto a = t{}; // Clang、GCC报错,MSVC通过 return true; }());
1. t a = t{};中a的初始化是否合法?
根据C++20标准,该初始化是合法的。
t属于聚合类型(数组是聚合的一种),t{}是对数组的聚合初始化,会生成一个同类型的临时数组prvalue。对于聚合类型的拷贝初始化,当右侧是同类型的prvalue时,标准规定会将prvalue的每个元素逐个初始化到目标聚合的对应元素(参考[dcl.init.aggr]第11款)。这里不存在数组对象的拷贝(数组确实不能被拷贝),而是元素级别的初始化,因此符合标准要求。Clang和MSVC的报错属于实现行为与标准不一致的情况。
2. 将a标记为constexpr是否会影响t a = t{};的初始化行为?C++20标准对此有何规定?
constexpr不会改变初始化的核心行为,仅增加了常量初始化的要求。
C++20中,constexpr变量要求初始化过程必须是常量表达式,且变量的初始化要满足常量初始化的规则。对于constexpr t a = t{};:
- 初始化逻辑和非
constexpr版本完全一致:依然是用临时数组prvalue的元素初始化目标数组的元素; - 额外要求:
t{}必须是常量表达式(这里int[1]{}显然满足,因为元素是值初始化的int常量),且整个初始化过程能在编译时完成。
只要满足常量表达式的要求,constexpr版本的合法性和非constexpr版本一致。
3. constexpr auto a = t{};的初始化情况如何?为何MSVC接受该写法?
根据C++20标准,该写法不合法,Clang和GCC的报错符合标准,MSVC属于实现扩展。
标准层面的分析:
auto推导数组prvalue时,会触发数组到指针的隐式转换,最终推导出的类型是int*(参考[dcl.type.auto.deduct]第2款)。此时constexpr auto a = t{};等价于constexpr int* a = &(t{})[0];:
- 临时数组
t{}是prvalue,其生命周期仅覆盖初始化表达式本身; - 在常量表达式中,不允许取临时数组的地址(参考[expr.const]第5款,临时对象的地址不能作为常量表达式的结果),因此该初始化违反标准,Clang和GCC的报错是正确的。
MSVC接受的原因:
MSVC可能实现了非标准的扩展:在constexpr auto初始化场景中,将数组prvaluet{}直接当作聚合初始化的列表处理,推导auto为数组类型t(即int[1]),此时constexpr auto a = t{};等价于constexpr t a = t{};——而后者符合标准,因此MSVC可以通过编译。但这属于MSVC的特化行为,不满足C++20标准的通用规则。
内容的提问来源于stack exchange,提问作者303

