Ori*_*ent 11 c++ initialization default-constructor language-lawyer c++14
应该在以下代码中调用哪个构造函数,为什么?
struct S
{
int i;
S() = default;
S(void *) : i{1} { ; }
};
S s{{}};
Run Code Online (Sandbox Code Playgroud)
如果我使用clang(来自主干),则调用第二个.
如果第二个构造函数被注释掉,那么S{{}}仍然是有效的表达式,但是(我相信)S{}在这种情况下调用默认构造的实例中的move-constructor .
为什么转换构造函数在第一种情况下优先于默认值?
构造函数的这种组合的目的S是保存其std::is_trivially_default_constructible_v< S >属性,除了有限的一组情况,当它应该以某种方式初始化时.
如果第二个构造函数被注释掉,那么S {{}}仍然是有效的表达式,但是(我确定)在这种情况下调用默认构造的S {}实例中的move-constructor.
实际上,这不是发生的事情.[dcl.init.list]中的顺序是:
类型T的对象或引用的列表初始化定义如下:
- 如果T是聚合类,并且初始化列表具有cv U类型的单个元素,则[...]
- 否则,如果T是字符数组[...]
- 否则,如果T是聚合,则执行聚合初始化(8.6.1).
删除S(void *)构造函数后,S成为聚合 - 它没有用户提供的构造函数.S() = default由于原因,不计入用户提供的数量.聚合初始化{}将最终初始化i成员.
为什么转换构造函数在第一种情况下优先于默认值?
随着void*剩下的,让我们保持下去子弹列表:
- 否则,如果初始化列表没有元素[...]
- 否则,如果T是std :: initializer_list的特化,[...]
- 否则,如果T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数.
[over.match.list]为我们提供了两阶段重载解析过程:
- 最初,候选函数是类T的初始化列表构造函数(8.6.4),参数列表由初始化列表作为单个参数组成.
- 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成.如果初始化列表没有元素且T具有默认构造函数,则省略第一个阶段.
S没有任何初始化列表构造函数,所以我们进入第二个项目符号并使用参数列表枚举所有构造函数{}.我们有多个可行的构造函数:
S(S const& );
S(S&& );
S(void *);
Run Code Online (Sandbox Code Playgroud)
转换序列在[over.ics.list]中定义:
否则,如果参数是非聚合类X并且每13.3.1.7的重载决策选择X的单个最佳构造函数C来从参数初始化列表执行类型X的对象的初始化:
- 如果C不是初始化器-list构造函数和初始化列表具有单个元素类型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-qualified)X,不考虑用户定义的转换序列.
这不包括考虑S(S const&)与S(S&& )作为候选-它们是正是这种情况下-目标是所述构造函数的第一参数为[over.match.list]第二阶段以及目标为基准,从而可能CV-合格的结果S,并且这样的转换序列将是用户定义的.
因此,唯一剩下的候选人是S(void *),因此它是最好的可行候选人.