phi*_*mue 15 c++ initialization clang visual-studio language-lawyer
#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;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码与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.
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
如果我在As构造函数中使用简单的括号(即A() : m_a() {}),它编译得很好.从cppreference我怀疑两者应该导致相同(即值初始化).我错过了什么,或者这是其中一个编译器中的错误?
son*_*yao 10
铿锵是对的.
你的困惑来自:
从cppreference我怀疑两者应该导致相同(即值初始化).
不,他们有不同的影响.请注意该页面中的注释:
在所有情况下,如果使用空的大括号{}并且T是聚合类型,则执行聚合初始化而不是值初始化.
这意味着当使用braced-init-list初始化时,对于聚合类型,首选聚合初始化.使用A() : m_a{} {},m_a是一个属于聚合类型的数组,然后执行聚合初始化:
(强调我的)
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)由空列表,按照通常的列表初始化规则(其执行值初始化用于非类类型和具有默认构造函数的非聚合类,以及聚合的聚合初始化).
这意味着,剩余的元素,即所有3个元素m_a将从空列表中复制初始化; 对于空列表,S将考虑默认构造函数,但它被声明为explicit; 该副本初始化不会调用explicit构造函数:
copy-list-initialization(考虑显式和非显式构造函数,但只能调用非显式构造函数)
在另一方面,A() : m_a() {}进行值初始化,然后
3)如果T是数组类型,则数组的每个元素都是值初始化的;
然后
1)如果T是没有默认构造函数的类类型,或者是用户提供或删除的默认构造函数,则该对象是默认初始化的;
然后S调用默认构造函数来初始化元素m_a.是否explicit与默认初始化无关.
为了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.2m_a表示我们从空初始化列表中复制初始化 的每个元素,即{}。
对于非联合聚合,每个不是显式初始化元素的元素都按如下方式初始化:
- [...]
- 否则,如果该元素不是引用,则该元素将从空初始值设定项列表 ([dcl.init.list]) 中复制初始化。
- [...]
这次我们点击[dcl.init.list]/3.5,它表示该元素已进行值初始化。
对象或类型引用的列表初始化
T定义如下:
- [...]
- 否则,如果初始值设定项列表没有元素并且
T是具有默认构造函数的类类型,则该对象将被值初始化。- [...]
这将我们带到[dcl.init]/8.1,它表示该元素是默认初始化的。
对类型的对象进行值初始化
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,它表示每个元素都是值初始化的。
对类型的对象进行值初始化
T意味着:
- [...]
- 如果
T是数组类型,则每个元素都是值初始化的;- [...]
这再次将我们带到[dcl.init]/8.1,然后到[dcl.init]/7.1,因此我们再次枚举每个[over.match.ctor]的构造函数并在初始化器上执行重载解析();
| 归档时间: |
|
| 查看次数: |
962 次 |
| 最近记录: |