双括号初始化

Double brace initialization

下面的代码应该调用哪个构造函数,为什么?

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

S s{{}};

如果我使用clang(从trunk),那么第二个被调用。

如果第二个构造函数被注释掉,那么 S{{}} 仍然是有效的表达式,但是(我相信)在这种情况下会调用 S{} 的默认构造实例中的移动构造函数。

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

S的构造函数的这种组合的目的是为了保存它的std::is_trivially_default_constructible_v< S >属性,除了有限的情况下,当它应该在某个特定的初始化方式。

If the second constructor is commented out, then S{{}} is still valid expression, but (I sure) move-constructor from default-constructed instance of S{} is called in the case.

实际上,事实并非如此。 [dcl.init.list] 中的顺序是:

List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate class and the initializer list has a single element of type cv U, [...]
— Otherwise, if T is a character array and [...]
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.6.1).

一旦删除了 S(void *) 构造函数,S 就变成了聚合 - 它没有用户提供的构造函数。 S() = default 由于某些原因,不算作用户提供。来自 {} 的聚合初始化将结束值初始化 i 成员。


Why conversion constructor has priority over the default one in the very first case?

剩下 void* 个,让我们继续往下看:

— Otherwise, if the initializer list has no elements [...]
— Otherwise, if T is a specialization of std::initializer_list, [...]
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).

[over.match.list]给了我们一个两阶段的过载解决过程:

— Initially, the candidate functions are the initializer-list constructors (8.6.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted.

S 没有任何初始化列表构造函数,因此我们进入第二个项目符号并使用 {} 的参数列表枚举所有构造函数。我们有多个可行的构造函数:

S(S const& );
S(S&& );
S(void *);

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

Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single best constructor C of X to perform the initialization of an object of type X from the argument initializer list:
— If C is not an initializer-list constructor and the initializer list has a single element of type cv U, [...] — Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.

Otherwise, if the parameter type is not a class: [...] — if the initializer list has no elements, the implicit conversion sequence is the identity conversion.

S(S&& )S(S const& )构造函数都是自定义转换序列加恒等转换。但是 S(void *) 只是身份转换。

但是,[over.best.ics] 有这个额外的规则:

However, if the target is
the first parameter of a constructor or
— the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
— 13.3.1.3, when [...]
— 13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases), or
the second phase of 13.3.1.7 when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,

user-defined conversion sequences are not considered.

这排除了 S(S const&)S(S&& ) 作为候选者的考虑 - 他们正是这种情况 - 作为 [[=61 第二阶段的结果,目标是构造函数的第一个参数=]] 并且目标是对可能的 cv 限定 S 的引用,这样的转换序列将是用户定义的。

因此,唯一剩下的候选者是 S(void *),因此它无疑是最可行的候选者。