双括号初始化
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 *)
,因此它无疑是最可行的候选者。
下面的代码应该调用哪个构造函数,为什么?
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 classX
, and the conversion is toX
or reference to (possibly cv-qualified)X
,user-defined conversion sequences are not considered.
这排除了 S(S const&)
和 S(S&& )
作为候选者的考虑 - 他们正是这种情况 - 作为 [[=61 第二阶段的结果,目标是构造函数的第一个参数=]] 并且目标是对可能的 cv 限定 S
的引用,这样的转换序列将是用户定义的。
因此,唯一剩下的候选者是 S(void *)
,因此它无疑是最可行的候选者。