简体   繁体   English

值初始化:MSVC vs clang

[英]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 members and bases (since C++17) are initialized by 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 sends us back to [dcl.init]/17.1 for the initialization of each element, which again sends us to [dcl.init.list] . 这将我们发送回[dcl.init] /17.1以进行每个元素的初始化,这再次将我们发送到[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". 此默认初始化在拷贝初始化的背景下,这样的候选功能是“那类的所有构造函数转换”。

  • The explicit default constructor is not a converting constructor. 显式默认构造函数不是转换构造函数。 As a result, there is no viable constructor. 结果,没有可行的构造函数。 Hence overload resolution fails, and the program is ill-formed. 因此,重载决策失败,程序格式不正确。

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 time, the default-initialization is not in the context of copy-initialization, so the candidate functions are "all the constructors of the class of the object being initialized". 这次,默认初始化不在复制初始化的上下文中,因此候选函数是“正在初始化的对象的类的所有构造函数”。
  • This time, the explicit default constructor is a candidate and selected by overload resolution. 这次,显式默认构造函数候选者并由重载决策选择。 So the program is well-formed. 所以该计划是完善的。

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

  1. 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

  1. 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

  1. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM