问题 双支撑初始化


应该在以下代码中调用哪个构造函数,为什么?

struct S
{
    int i;
    S() = default;
    S(void *) : i{1} { ; }
};

S s{{}};

如果我使用 clang (从主干),然后第二个被调用。

如果第二个构造函数被注释掉,那么 S{{}} 仍然是有效的表达式,但(我相信)move-constructor来自默认构造的实例 S{} 被称为案件。

为什么转换构造函数在第一种情况下优先于默认值?

这种结构的意图是建构者的 S 是为了保存它 std::is_trivially_default_constructible_v< S > 属性,除了有限的一组情况,它应该以某种方式初始化。


7818
2018-03-03 19:21


起源

“如果第二个构造函数被注释掉,那么S {{}}仍然是有效的表达式,但是(我确定)在这种情况下调用默认构造的S {}实例中的move-constructor。“不,它聚合初始化 S 用撑杆初始化 int。 - ildjarn
@ildjarn这个问题仍然存在。 - Orient


答案:


如果第二个构造函数被注释掉,那么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 *);

转换序列在[over.ics.list]中定义:

否则,如果参数是非聚合类X,则每13.3.1.7的重载决策选择一个   X的最佳构造函数C,用于从参数初始化列表中执行类型X的对象的初始化:
- 如果C不是初始化列表构造函数,并且初始化列表具有cv U类型的单个元素,[...]    - 否则,隐式转换序列是a 用户定义的转换序列,第二个标准转换序列是标识转换

否则,如果参数类型不是类:[...] - 如果初始化列表没有元素, 隐式转换序列是身份转换

那就是 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 或参考(可能是合格的) X

不考虑用户定义的转换序列。

这不考虑 S(S const&) 和 S(S&& ) 作为候选人 - 他们正是这种情况 - 目标是构造函数的第一个参数,因为[over.match.list]的第二阶段,目标是对可能的cv资格的引用 S,这样的转换序列将由用户定义。

因此,唯一剩下的候选人是 S(void *),所以这是最好的可行候选人。


9
2018-03-03 19:43



@ T.C我想我不确定第二个是否是标准转换序列。第1个明确表示为用户定义的...... - Barry
您的分析是正确的,除了一些细节:移动和复制构造函数不可行,因为16.3.3.1 [over.best.ics] p4“并且构造函数或用户定义的转换函数是候选者...第二个[over.match.list]的阶段,当初始化列表只有一个元素本身就是一个初始化列表时,目标是类X的构造函数的第一个参数,转换是X或引用到cv X. ..不考虑用户定义的转换序列。“ - Johannes Schaub - litb
如果它在删除用户提供的构造函数时不是聚合(例如通过拼写出default-constructor的定义),那么删除了转换构造函数的示例将不再编译。 - Johannes Schaub - litb
@ JohannesSchaub-litb OMG。有时我认为我理解C ++。然后有时我认为没有人理解C ++。会改变答案,谢谢你指出来。 - Barry