使用空的初始化列表直接初始化
Direct initialization with empty initializer list
struct X
{
X() { std::cout << "default ctor" << std::endl; }
};
int main()
{
X({});
}
这会打印出来
default ctor
这是有道理的,因为空大括号值初始化对象(我认为)。
然而,
struct X
{
X() { std::cout << "default ctor" << std::endl; }
X(std::initializer_list<int>) { std::cout << "initializer list" << std::endl; }
};
int main()
{
X({});
}
为此,我得到了
initializer list
我不觉得这种行为很奇怪,但我不完全相信。这是什么规则?
此行为是否写入标准的某些部分?
Is this behavior written in some part of the standard?
当然可以。这都是由 [dcl.init]/16 中的规则决定的,强调我的以匹配您的初始化程序:
The semantics of initializers are as follows. The destination type is
the type of the object or reference being initialized and the source
type is the type of the initializer expression. If the initializer is
not a single (possibly parenthesized) expression, the source type is
not defined.
If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized ([dcl.init.list]).
[...]
If the destination type is a (possibly cv-qualified) class type:
- If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated ([over.match.ctor]), and the best one is chosen through
overload resolution ([over.match]). The constructor so selected is
called to initialize the object, with the initializer expression or
expression-list as its argument(s). If no constructor applies, or the
overload resolution is ambiguous, the initialization is ill-formed.
- [...]
您提供了一个带括号的空大括号初始化列表,因此只有后面的项目符号适用。考虑构造函数,在第一种情况下,我们最终从默认初始化的 X
进行复制初始化。在后一种情况下,选择 initializer_list
c'tor 作为更好的匹配。选择此重载的规则在 [over.ics.list]:
中指定
When an argument is an initializer list ([dcl.init.list]), it is not
an expression and special rules apply for converting it to a parameter
type.
If the parameter type is std::initializer_list or “array of X” and
all the elements of the initializer list can be implicitly converted
to X, the implicit conversion sequence is the worst conversion
necessary to convert an element of the list to X. This conversion can
be a user-defined conversion even in the context of a call to an
initializer-list constructor.
Otherwise, if the parameter is a non-aggregate class X and overload
resolution per [over.match.list] chooses a single best constructor of
X to perform the initialization of an object of type X from the
argument initializer list, the implicit conversion sequence is a
user-defined conversion sequence. If multiple constructors are viable
but none is better than the others, the implicit conversion sequence
is the ambiguous conversion sequence. User-defined conversions are
allowed for conversion of the initializer list elements to the
constructor parameter types except as noted in [over.best.ics].
要查看实际情况,请声明复制和移动构造函数,在 C++14 模式或更早版本中编译,并禁用复制省略。
输出:
default ctor
move ctor
在第一个代码片段中,编译器查找带有单个参数的 X
的构造函数,因为您提供了单个参数。这些是复制和移动构造函数,X::X(const X&)
和 X::X(X&&)
,如果您不自己声明它们,编译器将隐式为您声明。然后,编译器使用默认构造函数将 {}
转换为 X
对象,并将该 X
对象传递给移动构造函数。 (您必须使用 fno-elide-constructors
才能看到这一点,否则编译器将省略移动,并且在 C++17 中复制省略成为强制性的。)
在第二个片段中,编译器现在可以选择将 {}
转换为 X
(然后调用移动构造函数),或将 {}
转换为 std::initializer_list<int>
(然后调用初始化列表构造函数)。根据[over.ics.list]/6.2,从{}
到调用默认构造函数的X
的转换是用户自定义转换,而根据[over.ics.list] /4,从{}
到std::initializer_list<int>
的转换就是恒等转换。身份转换优于用户定义转换,因此编译器调用初始化列表构造函数。
struct X
{
X() { std::cout << "default ctor" << std::endl; }
};
int main()
{
X({});
}
这会打印出来
default ctor
这是有道理的,因为空大括号值初始化对象(我认为)。 然而,
struct X
{
X() { std::cout << "default ctor" << std::endl; }
X(std::initializer_list<int>) { std::cout << "initializer list" << std::endl; }
};
int main()
{
X({});
}
为此,我得到了
initializer list
我不觉得这种行为很奇怪,但我不完全相信。这是什么规则?
此行为是否写入标准的某些部分?
Is this behavior written in some part of the standard?
当然可以。这都是由 [dcl.init]/16 中的规则决定的,强调我的以匹配您的初始化程序:
The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized ([dcl.init.list]).
[...]
If the destination type is a (possibly cv-qualified) class type:
- If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
- [...]
您提供了一个带括号的空大括号初始化列表,因此只有后面的项目符号适用。考虑构造函数,在第一种情况下,我们最终从默认初始化的 X
进行复制初始化。在后一种情况下,选择 initializer_list
c'tor 作为更好的匹配。选择此重载的规则在 [over.ics.list]:
When an argument is an initializer list ([dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
If the parameter type is std::initializer_list or “array of X” and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.
Otherwise, if the parameter is a non-aggregate class X and overload resolution per [over.match.list] chooses a single best constructor of X to perform the initialization of an object of type X from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence. If multiple constructors are viable but none is better than the others, the implicit conversion sequence is the ambiguous conversion sequence. User-defined conversions are allowed for conversion of the initializer list elements to the constructor parameter types except as noted in [over.best.ics].
要查看实际情况,请声明复制和移动构造函数,在 C++14 模式或更早版本中编译,并禁用复制省略。
输出:
default ctor
move ctor
在第一个代码片段中,编译器查找带有单个参数的 X
的构造函数,因为您提供了单个参数。这些是复制和移动构造函数,X::X(const X&)
和 X::X(X&&)
,如果您不自己声明它们,编译器将隐式为您声明。然后,编译器使用默认构造函数将 {}
转换为 X
对象,并将该 X
对象传递给移动构造函数。 (您必须使用 fno-elide-constructors
才能看到这一点,否则编译器将省略移动,并且在 C++17 中复制省略成为强制性的。)
在第二个片段中,编译器现在可以选择将 {}
转换为 X
(然后调用移动构造函数),或将 {}
转换为 std::initializer_list<int>
(然后调用初始化列表构造函数)。根据[over.ics.list]/6.2,从{}
到调用默认构造函数的X
的转换是用户自定义转换,而根据[over.ics.list] /4,从{}
到std::initializer_list<int>
的转换就是恒等转换。身份转换优于用户定义转换,因此编译器调用初始化列表构造函数。