嵌套列表初始化如何转发其参数?
How does nested list-initialization forward its arguments?
在对向量的初始化中
std::vector<std::pair<int, std::string>> foo{{1.0, "one"}, {2.0, "two"}};
我应该如何解释 foo
的结构?据我了解,
- 构造函数是用大括号初始化语法调用的,因此重载
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
是强烈推荐和选择的
std::initializer_list<T>
的模板参数推导为std::pair<int, std::string>
foo
的每个元素都是一个 std::pair
。但是,std::pair
没有接受 std::initializer_list
. 的重载
我不太确定第 3 步。我知道内括号不能解释为 std::initializer_list
,因为它们是异构的。标准中的什么机制实际上正在构造 foo
的每个元素?我怀疑答案与将内部大括号中的参数转发到重载 template< class U1, class U2 pair( U1&& x, U2&& y );
有关,但我不知道这叫什么。
编辑:
我认为提出相同问题的一种更简单的方法是:什么时候
std::map<int, std::string> m = { // nested list-initialization
{1, "a"},
{2, {'a', 'b', 'c'} },
{3, s1}
如 the cppreference example 所示,在标准中它说 {1, "a"}
、{2, {'a', 'b', 'c'} }
和 {3, s1}
每个都转发给 [=15 的构造函数=]?
您混淆了两个不同的概念:
1) 初始化列表
initializer_list<T>
: 主要用于集合的初始化。在这种情况下,所有成员都应该属于同一类型。 (不适用于 std::pair
)
示例:
std::vector<int> vec {1, 2, 3, 4, 5};
2)统一初始化
Uniform initialization
:其中大括号用于构造和初始化一些对象,如结构、类(使用适当的构造函数)和基本类型(整数、字符等)。
示例:
struct X { int x; std::string s;};
X x{1, "Hi"}; // Not an initializer_list here.
话虽如此,要使用大括号初始化程序初始化 std::pair
,您将需要一个包含两个元素的构造函数,即第一个和第二个元素,而不是 std::initializer_list<T>
。例如在我安装了 VS2015 的机器上,这个构造函数看起来像这样:
template<class _Other1,
class _Other2,
class = enable_if_t<is_constructible<_Ty1, _Other1>::value
&& is_constructible<_Ty2, _Other2>::value>,
enable_if_t<is_convertible<_Other1, _Ty1>::value
&& is_convertible<_Other2, _Ty2>::value, int> = 0>
constexpr pair(_Other1&& _Val1, _Other2&& _Val2) // -----> Here the constructor takes 2 input params
_NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
&& is_nothrow_constructible<_Ty2, _Other2>::value))
: first(_STD forward<_Other1>(_Val1)), // ----> initialize the first
second(_STD forward<_Other2>(_Val2)) // ----> initialize the second
{ // construct from moved values
}
通常,表达式是由内而外分析的:内部表达式有类型,然后这些类型决定外部运算符的含义以及要调用的函数。
但是初始化列表不是表达式,也没有类型。因此,由内而外是行不通的。需要特殊的重载决议规则来说明初始化列表。
第一条规则是:如果构造函数的单个参数是initializer_list<T>
,那么在第一轮重载决议中只考虑这样的构造函数(over.match.list)。
第二条规则是:对于每个 initializer_list<T>
个候选者(每个 class 可能有多个候选者,每个 T
个不同),检查每个初始化器可以转换为 T
,只有那些候选人保留在计算出的地方 (over.ics.list)。
这第二个规则基本上是,初始化列表没有类型的障碍被采取并恢复由内而外的分析。
一旦重载决议决定应使用特定的 initializer_list<T>
构造函数,复制初始化用于初始化 elements of type T
of the initializer 列表。
在对向量的初始化中
std::vector<std::pair<int, std::string>> foo{{1.0, "one"}, {2.0, "two"}};
我应该如何解释 foo
的结构?据我了解,
- 构造函数是用大括号初始化语法调用的,因此重载
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
是强烈推荐和选择的 std::initializer_list<T>
的模板参数推导为std::pair<int, std::string>
foo
的每个元素都是一个std::pair
。但是,std::pair
没有接受std::initializer_list
. 的重载
我不太确定第 3 步。我知道内括号不能解释为 std::initializer_list
,因为它们是异构的。标准中的什么机制实际上正在构造 foo
的每个元素?我怀疑答案与将内部大括号中的参数转发到重载 template< class U1, class U2 pair( U1&& x, U2&& y );
有关,但我不知道这叫什么。
编辑:
我认为提出相同问题的一种更简单的方法是:什么时候
std::map<int, std::string> m = { // nested list-initialization
{1, "a"},
{2, {'a', 'b', 'c'} },
{3, s1}
如 the cppreference example 所示,在标准中它说 {1, "a"}
、{2, {'a', 'b', 'c'} }
和 {3, s1}
每个都转发给 [=15 的构造函数=]?
您混淆了两个不同的概念:
1) 初始化列表
initializer_list<T>
: 主要用于集合的初始化。在这种情况下,所有成员都应该属于同一类型。 (不适用于 std::pair
)
示例:
std::vector<int> vec {1, 2, 3, 4, 5};
2)统一初始化
Uniform initialization
:其中大括号用于构造和初始化一些对象,如结构、类(使用适当的构造函数)和基本类型(整数、字符等)。
示例:
struct X { int x; std::string s;};
X x{1, "Hi"}; // Not an initializer_list here.
话虽如此,要使用大括号初始化程序初始化 std::pair
,您将需要一个包含两个元素的构造函数,即第一个和第二个元素,而不是 std::initializer_list<T>
。例如在我安装了 VS2015 的机器上,这个构造函数看起来像这样:
template<class _Other1,
class _Other2,
class = enable_if_t<is_constructible<_Ty1, _Other1>::value
&& is_constructible<_Ty2, _Other2>::value>,
enable_if_t<is_convertible<_Other1, _Ty1>::value
&& is_convertible<_Other2, _Ty2>::value, int> = 0>
constexpr pair(_Other1&& _Val1, _Other2&& _Val2) // -----> Here the constructor takes 2 input params
_NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value
&& is_nothrow_constructible<_Ty2, _Other2>::value))
: first(_STD forward<_Other1>(_Val1)), // ----> initialize the first
second(_STD forward<_Other2>(_Val2)) // ----> initialize the second
{ // construct from moved values
}
通常,表达式是由内而外分析的:内部表达式有类型,然后这些类型决定外部运算符的含义以及要调用的函数。
但是初始化列表不是表达式,也没有类型。因此,由内而外是行不通的。需要特殊的重载决议规则来说明初始化列表。
第一条规则是:如果构造函数的单个参数是initializer_list<T>
,那么在第一轮重载决议中只考虑这样的构造函数(over.match.list)。
第二条规则是:对于每个 initializer_list<T>
个候选者(每个 class 可能有多个候选者,每个 T
个不同),检查每个初始化器可以转换为 T
,只有那些候选人保留在计算出的地方 (over.ics.list)。
这第二个规则基本上是,初始化列表没有类型的障碍被采取并恢复由内而外的分析。
一旦重载决议决定应使用特定的 initializer_list<T>
构造函数,复制初始化用于初始化 elements of type T
of the initializer 列表。