繁体   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;
}

上面的代码可以很好地编译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我怀疑两者应该导致相同(即值初始化)。 我错过了什么,或者这是其中一个编译器中的错误?

铿锵是对的。

你的困惑来自:

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)空列表,按照通常的列表初始化规则(对非类型类和非聚合类使用默认构造函数执行值初始化,以及聚合初始化)对于聚合)。

这意味着,剩余的元素,即m_a所有3个元素将从空列表中复制初始化; 对于空列表,将考虑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 per [dcl.init.aggr]上执行聚合初始化。

    初始化器的语义如下。 [...]

    • 如果初始化程序是(非括号的) braced-init-list或is = braced-init-list ,则对象或引用将进行列表初始化。
    • [...]

    列表初始化对象或类型T引用定义如下:

    • [...]
    • 否则,如果T是聚合,则执行聚合初始化。
    • [...]
  • [dcl.init.aggr] /5.2表示我们从空的初始化列表中复制初始化m_a每个元素,即{}

    对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:

    • [...]
    • 否则,如果元素不是引用,则从空的初始化列表([dcl.init.list])复制初始化该元素。
    • [...]
  • 这将我们发送回[dcl.init] /17.1以进行每个元素的初始化,这再次将我们发送到[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]的构造函数并对初始化器()执行重载解析;

  • 这次,默认初始化不在复制初始化的上下文中,因此候选函数是“正在初始化的对象的类的所有构造函数”。
  • 这次,显式默认构造函数候选者并由重载决策选择。 所以该计划是完善的。

这标准明确不正确(问题是,为什么?):

m_a{} list-initializes S::m_a

[dcl.init.list]/1

列表初始化是从braced-init-list初始化对象或引用。 这样的初始化程序称为初始化程序列表,并且指定 初始化程序列表初始化程序列表指定初始化程序子句的逗号分隔的初始化程序子句称为初始化程序列表的元素。 初始化列表可以为空。 列表初始化可以在直接初始化复制初始化上下文中进行; 直接初始化上下文列表初始化被称为副本初始化上下文直接列表的初始化列表初始化被称为副本列表初始化

作为数组, A<S, 3>::m_a是聚合类型( [dcl.init.aggr]/1 )。

[dcl.init.aggr]/3.3

  1. 当聚合由[dcl.init.list]中指定的初始化列表初始化时,[...]
    3.3初始化列表必须是{} ,并且没有显式初始化的元素。

以下,因为没有明确初始化的元素

[dcl.init.aggr]/5.2

  1. 对于非联合聚合,每个不是显式初始化元素的元素都初始化如下:[...]
    5.2如果元素不是引用,则从空的初始化列表( [dcl.init.list]复制初始化该元素。

然后,对A<S, 3>::m_a每个S进行复制初始化

[dcl.init]/17.6.3

  1. 初始化器的语义如下。 目标类型是要初始化的对象或引用的类型, 源类型是初始化表达式的类型。 如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型。 [...]
    17.6如果目的地类型是(可能是cv限定的)类类型:[...]
    17.6.3否则(即,对于剩余的复制初始化情况), 可以按照描述枚举用户定义的转换序列,该转换序列可以从源类型转换为目标类型或(当使用转换函数时)到其派生类。在[over.match.copy]中,通过重载决策选择最好的一个。 如果转换不能完成或不明确,则初始化是错误的。

由于S的默认构造函数是显式的,因此无法从源类型转换为目标类型S )。

另一方面,使用m_a()的语法不是聚合成员初始化 ,也不调用复制初始化

如果我理解标准正确的铿锵是正确的。

根据[dcl.init.aggr] /8.5.1:2

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。 每个成员都是从相应的initializer子句复制初始化的。

并在同一条款[dcl.init.aggr] /8.5.1:7中进一步说明

如果列表中的初始化子条款少于聚合中的成员,则未明确初始化的每个成员应从其大括号或等号初始化程序初始化,或者,如果没有大括号或等于初始化程序,从一个空的初始化列表

根据列表初始化规则[over.match.list] /13.3.1.7

在copy-list-initialization中,如果选择了显式构造函数,则初始化是错误的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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