#include<cstddef>
template<typename T, std::size_t N>
struct A {
T m_a[N];
A() : m_a{} {}
};
struct S {
explicit S(int i=4) {}
};
int main() {
A<S, 3> an;
}
以上代码与MSVC(2017)编译良好,但在clang 3.8.0(输出clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp
)时失败:
clang version 3.8.0 (tags/RELEASE_380/final 263969)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
A() : m_a{} {}
^
main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
A<S, 3> an;
^
main.cpp:10:14: note: constructor declared here
explicit S(int i=4) {}
^
main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
A() : m_a{} {}
^
1 error generated.
clang 5.0也拒绝编译:
<source>:6:17: error: expected member name or ';' after declaration specifiers
A() : m_a{} {}
^
<source>:6:14: error: expected '('
A() : m_a{} {}
^
2 errors generated.
如果我在A
的构造函数中使用简单的括号(即A() : m_a() {}
),它编译得很好。从cppreference我怀疑两者都应该相同(即值初始化)。我错过了什么,或者这是其中一个编译器中的错误?
答案 0 :(得分:10)
Clang是对的。
你的困惑来自:
从cppreference我怀疑两者都应该相同(即值初始化)。
不,他们有不同的影响。请注意该页面中的注释:
在所有情况下,如果使用空的大括号{}并且T是聚合类型,则执行聚合初始化而不是值初始化。
这意味着当使用braced-init-list初始化时,对于聚合类型,首选聚合初始化。使用A() : m_a{} {}
,m_a
是一个属于aggregate type的数组,然后执行aggregate initialization:
(强调我的)
每个
direct public base, (since C++17)
数组元素或非静态类成员,按照类定义中数组下标/外观的顺序,从初始化列表的相应子句中复制初始化
和
如果初始化程序子句的数量少于成员数
and bases (since C++17)
或初始化程序列表完全为空,则其余成员and bases (since C++17)
将按空列表初始化by their default initializers, if provided in the class definition, and otherwise (since C++14)
,具体如下:通常的列表初始化规则(使用默认构造函数执行非类类型和非聚合类的值初始化,以及聚合的聚合初始化)。
这意味着,剩余的元素,即m_a
的所有3个元素将从空列表中复制初始化;对于空列表,将考虑S
的默认构造函数,但它被声明为explicit
; copy-initialization不会调用explicit
构造函数:
copy-list-initialization(考虑显式和非显式构造函数,但只能调用非显式构造函数)
另一方面,A() : m_a() {}
执行value initialization,然后执行
3)如果T是数组类型,则数组的每个元素都是值初始化的;
然后
1)如果T是没有默认构造函数的类类型,或者是用户提供或删除的默认构造函数,则该对象是默认初始化的;
然后调用S
的默认构造函数来初始化m_a
的元素。是explicit
是否与default initialization无关。
答案 1 :(得分:3)
m_a{}
:
[dcl.init]/17.1将我们发送至[dcl.init.list],[dcl.init.list]/3.4表示我们按m_a
每[dcl.init.aggr]执行汇总初始化。
初始化器的语义如下。 [...]
- 如果初始化程序是(非括号内的) braced-init-list 或
=
braced-init-list ,则对象或引用是列表初始化。- [...]
类型
T
的对象或引用的列表初始化定义如下:
- [...]
- 否则,如果
T
是聚合,则执行聚合初始化。- [...]
[dcl.init.aggr]/5.2表示我们会从空的初始值设定项列表中复制初始化m_a
的每个元素,即{}
。
对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:
- [...]
- 否则,如果元素不是引用,则从空的初始化列表([dcl.init.list])复制初始化该元素。
- [...]
这次我们点击[dcl.init.list]/3.5,表示该元素已初始化。
类型
T
的对象或引用的列表初始化定义如下:
- [...]
- 否则,如果初始化列表没有元素且
T
是具有默认构造函数的类类型,则该对象是值初始化的。- [...]
这将我们带到[dcl.init]/8.1,它表示该元素是默认初始化的。
value-initialize
T
类型的对象意味着:
- 如果
T
是一个(可能是cv限定的)类类型,没有默认构造函数([class.ctor])或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;- [...]
哪次点击[dcl.init]/7.1,表示我们按照[over.match.ctor]枚举构造函数并对初始化程序()
执行重载解析;
默认初始化
T
类型的对象意味着:
- 如果
T
是(可能是cv限定的)类类型,则考虑构造函数。列举了适用的构造函数 ([over.match.ctor]),初始化程序()
的最佳选择是 通过重载决议选择。这样选择的构造函数是 使用空参数列表调用以初始化对象。- [...]
和[over.match.ctor]说:
对于直接初始化或默认初始化不在 复制初始化的上下文,候选函数都是 正在初始化的对象类的构造函数。对于 复制初始化,候选函数都是转换 该班的建设者。
此默认初始化在复制初始化的上下文中是,因此候选函数是“该类的所有转换构造函数”。
m_a()
:
我们点击[dcl.init]/17.4,表示数组已初始化值。
初始化器的语义如下。 [...]
- [...]
- 如果初始值设定项为
()
,则对象将进行值初始化。- [...]
这将我们带到[dcl.init]/8.3,它说每个元素都是值初始化的。
value-initialize
T
类型的对象意味着:
- [...]
- 如果
T
是数组类型,则每个元素都是值初始化的;- [...]
这再次将我们带到[dcl.init]/8.1,然后再转到[dcl.init]/7.1,因此我们再次按[over.match.ctor]枚举构造函数并对初始化程序()
执行重载解析
答案 2 :(得分:2)
这标准明确不正确(问题是,为什么?):
m_a{}
列表初始化S::m_a
:
[dcl.init.list]/1
列表初始化是从 braced-init-list 初始化对象或引用。 这样的初始化程序称为初始化程序列表,初始化程序列表或指定的初始化程序子句的逗号分隔初始化程序子句 specified-initializer-list 被称为初始化列表的元素。初始化列表可以为空。列表初始化可以在直接初始化或复制初始化上下文中进行; 直接初始化上下文中的 list-initialization 称为 direct-list-initialization 和 list-initialization copy-initialization 上下文称为 copy-list-initialization 。
作为数组,A<S, 3>::m_a
是聚合类型([dcl.init.aggr]/1
)。
[dcl.init.aggr]/3.3
- 当聚合由[dcl.init.list]中指定的初始化列表初始化时,[...]
醇>
3.3初始化列表必须为{}
,并且没有明确初始化的元素。
以下,因为没有明确初始化的元素:
[dcl.init.aggr]/5.2
- 对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:[...]
醇>
5.2如果元素不是引用,则元素从空的初始化列表([dcl.init.list]
)复制初始化。
S
的每个A<S, 3>::m_a
,然后复制初始化:
[dcl.init]/17.6.3
- 初始化器的语义如下。 目标类型是要初始化的对象或引用的类型,源类型是初始化表达式的类型。 如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型。 [...]
醇>
17.6如果目的地类型是(可能是cv限定的)类类型:[...]
17.6.3否则(即,对于剩余的复制初始化情况),用户定义的转换序列可以将从源类型转换为目标类型或(当转换函数用于其派生类,如[over.match.copy]中所述列举,并且通过重载决策选择最佳的一个。 如果转换无法完成或模糊不清,则初始化格式不正确。
由于S
的默认构造函数是显式的,因此无法从源类型转换为目标类型(S
)。
另一方面,使用m_a()
的语法不是聚合成员初始化,并且不会调用复制初始化。
答案 3 :(得分:0)
如果我理解标准正确的铿锵是正确的。
根据[dcl.init.aggr] /8.5.1:2
按指定的初始化列表初始化聚合时 在8.5.4中,初始化列表的元素被视为 增加下标的聚合成员的初始化程序 或会员订单。每个成员都是从中复制初始化的 相应的初始化条款。
进一步在同一条款中[dcl.init.aggr] /8.5.1:7
如果列表中的初始化子句数少于 聚合中的成员,然后每个成员未明确初始化 应从其支撑或等于初始化器初始化,或者如果有的话 从空的初始化列表
中没有大括号或等于初始化器
根据列表初始化规则[over.match.list] /13.3.1.7
在copy-list-initialization中,如果选择了显式构造函数,则 初始化是不正确的。