双括号初始化
应该在下面的代码中调用哪个构造函数,为什么?双括号初始化
struct S
{
int i;
S() = default;
S(void *) : i{1} { ; }
};
S s{{}};
如果使用clang
(从主干),那么第二个被调用。
如果第二个构造函数被注释掉了,那么S{{}}
仍然是有效的表达式,但是(我相信)在这种情况下调用了从默认构造的S{}
实例中移动构造函数。
为什么转换构造函数在第一种情况下的优先级高于默认值?
的S
的构造的这种组合的目的是保存其std::is_trivially_default_constructible_v<S>
属性,除了一组有限的情况下,当它应该以某种方式被初始化的。
如果第二个构造函数被注释掉了,那么S {{}}仍然是有效的表达式,但是在我的情况下调用了S {}的默认构造实例的move-constructor。
实际上,那不会发生什么。在[dcl.init.list]的顺序是:
对象或类型T的参考的列表的初始化被定义如下:
- 如果T是一个聚合类和初始化列表具有单个[...]类型cv U的元素,
- 否则,如果T是一个字符数组,
一旦您删除S(void *)
构造函数,S
将成为聚合 - 它没有用户提供的构造函数。由于原因,S() = default
不计为用户提供。来自{}
的总体初始化将最终对i
成员进行初始化。
为什么转换构造了一个默认的第一个案件有优先权?
随着void*
剩下的,让我们继续下去的项目列表:
- 否则,如果初始化列表中没有的元素[...]
- 否则,如果T是一个专业化std :: initializer_list,[...]
- 否则,如果T是类类型,则考虑构造函数。列举的适用构造函数为 ,最好的是通过重载分辨率(13.3,13.3.1.7)选择的。
[over.match.list]为我们提供了两相重载解析过程:
- 最初,候选功能初始化列表构造器(8.6。4)T和 参数列表由初始化程序列表作为单个参数组成。
- 如果没有找到可行的初始化列表构造函数,重载决议再次进行,其中 候选函数是T类的所有构造函数和参数列表由元素初始化列表的 的。如果初始化列表没有任何元素,T具有一个默认的构造,省略了第一阶段。
S
没有任何初始化列表构造函数,所以我们进入第二个项目符号和枚举所有的{}
参数列表的构造函数。我们有多个可行的构造:
S(S const&);
S(S&&);
S(void *);
转换序列在[over.ics.list]定义:
否则,如果参数是每13.3非聚合类X和重载解析.1.7选择X的单个 最好构造C到执行从参数初始化列表X类型的对象的初始化:
- 如果C不是一个初始化列表构造器和初始化列表具有式CV的单个元件U,[...] - 否则,隐式转换序列是用户定义的转换序列与第二个标准转换序列的标识转换。
和
否则,如果参数类型是不是一类:[...] - 如果初始化列表没有任何元素,的隐式转换的序列是恒等变换。
即,S(S&&)
和S(S const&)
构造都是用户定义的转换序列加上标识转换。但S(void *)
只是一个身份转换。
但是,[over.best.ics]有这样额外的规则:
然而,如果目标是
- 构造或
的第一个参数 - 隐含的对象参数用户定义的转换函数
和构造或用户定义的转换函数是通过
候选 - 13.3.1.3,当[...]
- 13.3.1.4,13.3.1.5,或13.3.1.6(在所有情况下),或
- 的13.3.1.7第二阶段时初始化列表具有恰好一个要素即本身初始化值列表,并且目标是X
类的构造函数的第一参数,以及转换是X
或参考(可能是cv合格的)X
,不考虑用户定义的转换序列。
这从考虑S(S const&)
和S(S&&)
排除作为候选 - 它们是正是这种情况下 - 目标是所述构造的作为[over.match.list]第二阶段和所述目标的结果的第一参数作为一个参考,从而可能CV-合格S
,并且这样的转换序列将是用户定义的。
因此,剩下的唯一候选人是S(void *)
,所以它是平凡的最佳可行的候选人。
@ T.C我想我不知道,如果:第二个是一个标准的转换序列。第一种是明确声明为用户定义的... – Barry
除了一点细节之外,您的分析是正确的:移动和复制构造函数不可行,因为16.3.3.1 [over.best.ics] p4“以及构造函数或用户定义的转换函数是一个候选者,通过... [over.match.list]的第二个阶段,当初始化器列表恰好有一个元素本身就是一个初始化器列表,并且目标是类X的构造器的第一个参数,并且转换为X或对cv X的引用...不考虑用户定义的转换序列。“ –
如果在他移除用户提供的构造函数时(例如,通过拼写出default-constructor的定义),它不是一个聚合函数,那么删除其转换构造函数的示例将不再编译。 –
“*如果第二个构造已被注释掉,则S {{}}仍然是有效的表达,但(我知道)移动构造函数从第{}被调用的情况下默认构造的实例。*”号它会聚合 - 用'brace'初始化'int'来初始化'S'。 – ildjarn
@ildjarn问题是仍然有效。 – Orient