[英]Value initialization: MSVC vs clang
#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;
}
The above code compiles fine with MSVC (2017), but fails with clang 3.8.0 (Output of clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp
): 上面的代码可以很好地编译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 also refuses to compile this: 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.
If I use simple parentheses in A
s constructor to (ie A() : m_a() {}
), it compiles fine. 如果我在
A
的构造函数中使用简单的括号(即A() : m_a() {}
),它编译得很好。 From cppreference I would have suspected that both should result in the same (ie value initialization). 从cppreference我怀疑两者应该导致相同(即值初始化)。 Am I missing something or is this a bug in one of the compilers?
我错过了什么,或者这是其中一个编译器中的错误?
Clang is correct. 铿锵是对的。
Your confusion comes from: 你的困惑来自:
From cppreference I would have suspected that both should result in the same (ie value initialization).
从cppreference我怀疑两者应该导致相同(即值初始化)。
No they have different effects. 不,他们有不同的影响。 Note the notes in that page:
请注意该页面中的注释:
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
在所有情况下,如果使用空的大括号{}并且T是聚合类型,则执行聚合初始化而不是值初始化。
That means when initialized with braced-init-list, for aggregate type, aggregate-initialization is preferred to be performed. 这意味着当使用braced-init-list初始化时,对于聚合类型,首选聚合初始化。 With
A() : m_a{} {}
, and m_a
is an array, which belongs to aggregate type , then aggregate initialization is performed instead: 使用
A() : m_a{} {}
,并且m_a
是一个属于聚合类型的数组,然后执行聚合初始化 :
(emphasis mine) (强调我的)
Each
direct public base, (since C++17)
array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.每个
direct public base, (since C++17)
数组元素或非静态类成员,按照类定义中的数组下标/外观的顺序,从初始化列表的相应子句进行复制初始化。
and 和
If the number of initializer clauses is less than the number of members
and bases (since C++17)
or initializer list is completely empty, the remaining membersand bases (since C++17)
are initializedby their default initializers, if provided in the class definition, and otherwise (since C++14)
by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).如果初始化程序子句的数量小于成员
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)
空列表,按照通常的列表初始化规则(对非类型类和非聚合类使用默认构造函数执行值初始化,以及聚合初始化)对于聚合)。
That means, the remaining elements, ie all the 3 elements of m_a
will be copy-initialized from the empty list; 这意味着,剩余的元素,即
m_a
所有3个元素将从空列表中复制初始化; for empty list the default constructor of S
will be considered but it's declared as explicit
; 对于空列表,将考虑
S
的默认构造函数,但它被声明为explicit
; the copy-initialization won't invoke explicit
constructors: 复制初始化不会调用
explicit
构造函数:
copy-list-initialization (both explicit and non-explicit constructors are considered, but only non-explicit constructors may be called)
copy-list-initialization(考虑显式和非显式构造函数,但只能调用非显式构造函数)
On the other hand, A() : m_a() {}
performs value initialization , then 另一方面,
A() : m_a() {}
执行值初始化
3) if T is an array type, each element of the array is value-initialized;
3)如果T是数组类型,则数组的每个元素都是值初始化的;
then 然后
1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;
1)如果T是没有默认构造函数的类类型,或者是用户提供或删除的默认构造函数,则该对象是默认初始化的;
then the default constructor of S
is invoked to initialize the elements of m_a
. 然后调用
S
的默认构造函数来初始化m_a
的元素。 Whether it's explicit
or not doesn't matter for default initialization . 它是否
explicit
与默认初始化无关。
For m_a{}
: 对于
m_a{}
:
[dcl.init]/17.1 sends us to [dcl.init.list] , and [dcl.init.list]/3.4 says that we perform aggregate initialization on m_a
per [dcl.init.aggr] . [dcl.init] /17.1将我们发送到[dcl.init.list] , [dcl.init.list] /3.4表示我们在
m_a
per [dcl.init.aggr]上执行聚合初始化。
The semantics of initializers are as follows.
初始化器的语义如下。 [...]
[...]
- If the initializer is a (non-parenthesized) braced-init-list or is
=
braced-init-list , the object or reference is list-initialized.如果初始化程序是(非括号的) braced-init-list或is
=
braced-init-list ,则对象或引用将进行列表初始化。- [...]
[...]
List-initialization of an object or reference of type
T
is defined as follows:列表初始化对象或类型
T
引用定义如下:
- [...]
[...]
- Otherwise, if
T
is an aggregate, aggregate initialization is performed.否则,如果
T
是聚合,则执行聚合初始化。- [...]
[...]
[dcl.init.aggr]/5.2 says that we copy-initialize each element of m_a
from an empty initializer list, ie, {}
. [dcl.init.aggr] /5.2表示我们从空的初始化列表中复制初始化
m_a
每个元素,即{}
。
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:
- [...]
[...]
- Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
否则,如果元素不是引用,则从空的初始化列表([dcl.init.list])复制初始化该元素。
- [...]
[...]
This time we hit [dcl.init.list]/3.5 , which says that the element is value-initialized. 这次我们点击了[dcl.init.list] /3.5 ,它表示该元素是值初始化的。
List-initialization of an object or reference of type
T
is defined as follows:列表初始化对象或类型
T
引用定义如下:
- [...]
[...]
- Otherwise, if the initializer list has no elements and
T
is a class type with a default constructor, the object is value-initialized.否则,如果初始化程序列表没有元素且
T
是具有默认构造函数的类类型,则对象将进行值初始化。- [...]
[...]
Which brings us to [dcl.init]/8.1 , which says that the element is default-initialized. 这将我们带到[dcl.init] /8.1 ,它表示该元素是默认初始化的。
To value-initialize an object of type
T
means:对值类型
T
的对象进行值初始化意味着:
- if
T
is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;如果
T
是一个(可能是cv限定的)类类型,没有默认构造函数([class.ctor])或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;- [...]
[...]
Which hits [dcl.init]/7.1 , which says we enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ()
; 其中命中[dcl.init] /7.1 ,表示我们按[over.match.ctor]枚举构造函数并对初始化程序
()
执行重载解析;
To default-initialize an object of type
T
means:默认初始化
T
类型的对象意味着:
- If
T
is a (possibly cv-qualified) class type, constructors are considered.如果
T
是(可能是cv限定的)类类型,则考虑构造函数。 The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer()
is chosen through overload resolution.枚举适用的构造函数([over.match.ctor]),并通过重载决策选择初始化程序
()
的最佳构造函数。 The constructor thus selected is called, with an empty argument list, to initialize the object.使用空参数列表调用如此选择的构造函数来初始化对象。
- [...]
[...]
and [over.match.ctor] says: 和[over.match.ctor]说:
For direct-initialization or default-initialization that is not in the context of copy-initialization , the candidate functions are all the constructors of the class of the object being initialized.
对于不在复制初始化上下文中的直接初始化或默认初始化 ,候选函数是要初始化的对象的类的所有构造函数。 For copy-initialization, the candidate functions are all the converting constructors of that class.
对于复制初始化,候选函数是该类的所有转换构造函数。
This default-initialization is in the context of copy-initialization, so the candidate functions are "all the converting constructors of that class". 此默认初始化是在拷贝初始化的背景下,这样的候选功能是“那类的所有构造函数转换”。
For m_a()
: 对于
m_a()
:
We hit [dcl.init]/17.4 , which says that the array is value-initialized. 我们点击[dcl.init] /17.4 ,表示该数组已初始化。
The semantics of initializers are as follows.
初始化器的语义如下。 [...]
[...]
- [...]
[...]
- If the initializer is
()
, the object is value-initialized.如果初始化程序为
()
,则对象进行值初始化。- [...]
[...]
Which brings us to [dcl.init]/8.3 , which says that each element is value-initialized. 这将我们带到[dcl.init] /8.3 ,它表示每个元素都是值初始化的。
To value-initialize an object of type
T
means:对值类型
T
的对象进行值初始化意味着:
- [...]
[...]
- if
T
is an array type, then each element is value-initialized;如果
T
是数组类型,那么每个元素都是值初始化的;- [...]
[...]
Which again brings us to [dcl.init]/8.1 , and then to [dcl.init]/7.1 , and so we again enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ()
; 这再次将我们带到[dcl.init] /8.1 ,然后再到[dcl.init] /7.1 ,因此我们再次枚举每个[over.match.ctor]的构造函数并对初始化器
()
执行重载解析;
This is explicitly ill-formed by the Standard (the question is, though, why?): 这标准明确不正确(问题是,为什么?):
m_a{}
list-initializes the S::m_a
: m_a{}
list-initializes S::m_a
:
[dcl.init.list]/1
List-initialization is initialization of an object or reference from a braced-init-list .
列表初始化是从braced-init-list初始化对象或引用。 Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the initializer-list or designated-initializer-clauses of the designated-initializer-list are called the elements of the initializer list.
这样的初始化程序称为初始化程序列表,并且指定 初始化程序列表的初始化程序列表或指定初始化程序子句的逗号分隔的初始化程序子句称为初始化程序列表的元素。 An initializer list may be empty.
初始化列表可以为空。 List-initialization can occur in direct-initialization or copy-initialization contexts;
列表初始化可以在直接初始化或复制初始化上下文中进行; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization .
在直接初始化上下文列表初始化被称为副本初始化上下文直接列表的初始化和列表初始化被称为副本列表初始化 。
As an array, A<S, 3>::m_a
is an aggregate type ( [dcl.init.aggr]/1
). 作为数组,
A<S, 3>::m_a
是聚合类型( [dcl.init.aggr]/1
)。
[dcl.init.aggr]/3.3
- When an aggregate is initialized by an initializer list as specified in [dcl.init.list], [...]
当聚合由[dcl.init.list]中指定的初始化列表初始化时,[...]
3.3 the initializer list must be{}
, and there are no explicitly initialized elements.3.3初始化列表必须是
{}
,并且没有显式初始化的元素。
following, since there are no explicitly initialized elements : 以下,因为没有明确初始化的元素 :
[dcl.init.aggr]/5.2
- For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows: [...]
对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:[...]
5.2 if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]
).5.2如果元素不是引用,则从空的初始化列表(
[dcl.init.list]
) 复制初始化该元素。
Each S
of A<S, 3>::m_a
is, then, copy-initialized : 然后,对
A<S, 3>::m_a
每个S
进行复制初始化 :
[dcl.init]/17.6.3
- The semantics of initializers are as follows.
初始化器的语义如下。 The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.
目标类型是要初始化的对象或引用的类型, 源类型是初始化表达式的类型。 If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型。 [...]
[...]
17.6 If the destination type is a (possibly cv-qualified) class type: [...]17.6如果目的地类型是(可能是cv限定的)类类型:[...]
17.6.3 Otherwise (ie, for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution.17.6.3否则(即,对于剩余的复制初始化情况), 可以按照描述枚举用户定义的转换序列,该转换序列可以从源类型转换为目标类型或(当使用转换函数时)到其派生类。在[over.match.copy]中,通过重载决策选择最好的一个。 If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
如果转换不能完成或不明确,则初始化是错误的。
Since the default constructor of S
is explicit, it cannot convert from the source type to the destination type ( S
). 由于
S
的默认构造函数是显式的,因此无法从源类型转换为目标类型 ( S
)。
The syntax using m_a()
is, on the other hand, not aggregate member initialization and does not invoke copy-initialization . 另一方面,使用
m_a()
的语法不是聚合成员初始化 ,也不调用复制初始化 。
If I understand the standard correctly clang is correct. 如果我理解标准正确的铿锵是正确的。
According to [dcl.init.aggr]/8.5.1:2 根据[dcl.init.aggr] /8.5.1:2
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order.
当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。 Each member is copy-initialized from the corresponding initializer-clause.
每个成员都是从相应的initializer子句复制初始化的。
And further down in the same clause [dcl.init.aggr]/8.5.1:7 并在同一条款[dcl.init.aggr] /8.5.1:7中进一步说明
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal- initializer, from an empty initializer list
如果列表中的初始化子条款少于聚合中的成员,则未明确初始化的每个成员应从其大括号或等号初始化程序初始化,或者,如果没有大括号或等于初始化程序,从一个空的初始化列表
According to the rules for list initialization [over.match.list]/13.3.1.7 根据列表初始化规则[over.match.list] /13.3.1.7
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
在copy-list-initialization中,如果选择了显式构造函数,则初始化是错误的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.