首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >值初始化: MSVC vs clang

值初始化: MSVC vs clang
EN

Stack Overflow用户
提问于 2018-02-19 16:43:51
回答 3查看 1.2K关注 0票数 15
代码语言:javascript
复制
#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)中编译得很好,但是clang3.8.0(clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp的输出)却失败了:

代码语言:javascript
复制
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还拒绝编译以下内容:

代码语言:javascript
复制
<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() {}),它会编译得很好。从优先选择中,我会怀疑两者都应该导致相同的结果(即值初始化)。我是遗漏了什么,还是这是编译器中的一个bug?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-02-19 20:50:53

对于m_a{}

代码语言:javascript
复制
- If the initializer is a (non-parenthesized) _braced-init-list_ or is `=` _braced-init-list_, the object or reference is list-initialized.
- [...]

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

代码语言:javascript
复制
- [...]
- Otherwise, if `T` is an aggregate, aggregate initialization is performed.
- [...]
  • [dcl.init.aggr]/5.2说,我们从空初始化程序列表(即{} )中复制-初始化m_a的每个元素。 对于不合并聚合,未显式初始化元素的每个元素初始化如下:
代码语言:javascript
复制
- [...]
- Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
- [...]
  • 这会将我们送回[dcl.init]/17.1进行每个元素的初始化,这再次将我们发送到[dcl.init.list]
  • 这一次我们使用[dcl.init.list]/3.5,它表示元素是值初始化的。 列表-对象的初始化或T类型的引用定义如下:
代码语言:javascript
复制
- [...]
- Otherwise, if the initializer list has no elements and `T` is a class type with a default constructor, the object is value-initialized.
- [...]
  • 这就引出了[dcl.init]/8.1,它说元素是默认初始化的。 要对T类型的对象进行值初始化,意味着:
代码语言:javascript
复制
- 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;
- [...]
  • 它点击[dcl.init]/7.1,它要求我们枚举每个[over.match.ctor]的构造函数,并在初始化器()上执行过载解析; 默认情况下-初始化T类型的对象意味着:
代码语言:javascript
复制
- If `T` is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated   ([over.match.ctor]), and the best one for the _initializer_ `()` is   chosen through overload resolution. The constructor thus selected is   called, with an empty argument list, to initialize the object.
- [...]
  • over.match.ctor说: 对于直接初始化或不在复制初始化上下文中的默认初始化,候选函数都是被初始化对象类的构造函数。对于复制初始化,候选函数都是该类的转换构造函数.
  • 这种默认初始化是在复制初始化的上下文中进行的,因此候选函数是“该类的所有转换构造函数”。
  • 显式默认构造函数不是转换构造函数。因此,没有可行的构造函数。因此,过载解析失败,程序是错误的.

对于m_a()

  • 我们点击[dcl.init]/17.4,它表示数组是值初始化的。 初始化器的语义如下。..。
代码语言:javascript
复制
- [...]
- If the initializer is `()`, the object is value-initialized.
- [...]
  • 这就引出了[dcl.init]/8.3,它说每个元素都是值初始化的。 要对T类型的对象进行值初始化,意味着:
代码语言:javascript
复制
- [...]
- if `T` is an array type, then each element is value-initialized;
- [...]
  • 这再次将我们带到[dcl.init]/8.1,然后是[dcl.init]/7.1,因此我们再次枚举每个[over.match.ctor]的构造函数,并对初始化器()执行过载解析;
  • 这一次,默认初始化不在复制初始化的上下文中,因此候选函数是“被初始化对象的类的所有构造函数”。
  • 这一次,显式默认构造函数是一个候选函数,并由重载解析选择。所以这个程序的格式很好。
票数 3
EN

Stack Overflow用户

发布于 2018-02-19 17:26:50

嘎嘎声是对的。

你的困惑来自:

优先选择中,我会怀疑两者都应该导致相同的结果(即值初始化)。

不,它们有不同的效果。请注意该页的说明:

在所有情况下,如果使用空大括号{}且T是聚合类型,则执行聚合初始化而不是值初始化。

这意味着当使用大括号-init-list初始化时,对于聚合类型,首选执行聚合初始化。使用A() : m_a{} {}时,m_a是一个属于集料类型的数组,然后执行聚合初始化

(强调地雷)

按照类定义中数组下标/外观的顺序,每个copy-initialized数组元素或非静态类成员都是初始化程序列表对应子句中的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构造函数:

复制列表初始化(同时考虑显式和非显式构造函数,但只能调用非显式构造函数)

另一方面,A() : m_a() {}执行值初始化,然后

3)如果T是数组类型,则数组中的每个元素都是值初始化的;

然后

1)如果T是一个没有默认构造函数的类类型,或者是用户提供或删除的默认构造函数,则该对象是默认初始化的;

然后调用S的默认构造函数来初始化m_a的元素。它是否是explicit默认初始化来说并不重要。

票数 10
EN

Stack Overflow用户

发布于 2018-02-19 17:03:07

这显然是标准的错误(问题是,为什么?):

m_a{}列表-初始化S::m_a

[dcl.init.list]/1 列表初始化是从大括号内的列表中初始化对象或引用.这样的初始化器称为初始化程序列表,指定初始化程序列表的逗号分隔的初始化程序-子句或指定-初始化-子句称为初始化程序列表的元素。初始化程序列表可能为空。列表初始化可以在直接初始化或复制初始化上下文中进行;直接初始化上下文中的列表初始化称为直接列表初始化,副本初始化上下文中的列表初始化称为复制列表初始化。

作为数组,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. 对于不合并聚合,未显式初始化元素的每个元素初始化如下: 如果元素不是引用,则元素是从空初始化程序列表([dcl.init.list])复制初始化的。

然后,每个S of A<S, 3>::m_a都被复制初始化。

[dcl.init]/17.6.3

  1. 初始化器的语义如下。目标类型是正在初始化的对象或引用的类型,源类型是初始化器表达式的类型。如果初始化器不是单个(可能是括号大小)表达式,则不定义源类型。..。 17.6如果目标类型是(可能是cv限定的)类类型:. 17.6.3否则(即,对于剩余的副本初始化情况),用户定义的转换序列可以将从源类型转换为目标类型,或者(当使用转换函数时)到派生类的转换序列,如over.match.copy中所描述的那样,并且通过过载解析选择最佳的转换序列。如果转换不能完成或不明确,则初始化是错误的.

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

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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48870550

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档