考虑以下代码:
#include <initializer_list>
#include <utility>
template<class T>
struct test
{
test(const std::pair<T, T> &)
{}
};
template<class T>
test(std::initializer_list<T>) -> test<T>;
int main()
{
test t{{1, 2}};
}
我想了解为什么initializer_list
会编译这个技巧。一开始,{1,2}被视为initializer_list
,但后来它被重新解释为pair
的列表初始化。
这里一步一步到底发生了什么?
它之所以编译,是因为类模板推导指南就是这样工作的。
推导指南是该类型的假设构造函数。它们并不真的存在。它们的唯一目的是确定如何推导类模板参数。
一旦进行了推导,实际的C++代码就会接管test
的特定实例化。因此,编译器的行为就像您说的test<int> t{{1, 2}};
一样,而不是test t{{1, 2}};
。
test<int>
有一个接受pair<int, int>
的构造函数,它可以与支撑的init列表中的值相匹配,所以这就是调用的内容。
这样做的部分原因是允许聚合参与类模板参数推导。聚合没有用户提供的构造函数,所以如果推理指南仅限于真正的构造函数,那么聚合就无法工作。
因此,我们有了std::array
:的类模板推导指南
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
这允许std::array arr = {2, 4, 6, 7};
工作。它从指南中推导出模板参数和长度,但由于指南不是构造函数,array
仍然是一个聚合。
有了您的推导指南,我们最终得出的结果相当于:
test<int> t{{1, 2}};
这是由于列表初始化,dcl.init.listp3.7节说:
否则,如果T是类类型,则考虑构造函数。这个枚举适用的构造函数并选择最佳构造函数通过重载解析([over.match],[over.maching.list](。如果要转换任何参数,程序格式不正确。[示例:
struct S { S(std::initializer_list<double>); // #1 S(std::initializer_list<int>); // #2 S(); // #3 // ... }; S s1 = { 1.0, 2.0, 3.0 }; // invoke #1 S s2 = { 1, 2, 3 }; // invoke #2 S s3 = { }; // invoke #3
--结束示例][示例:
struct Map { Map(std::initializer_list<std::pair<std::string,int>>); }; Map ship = {{"Sophie",14}, {"Surprise",28}};
--结束示例][示例:
struct S { // no initializer-list constructors S(int, double, double); // #1 S(); // #2 // ... }; S s1 = { 1, 2, 3.0 }; // OK: invoke #1 S s2 { 1.0, 2, 3 }; // error: narrowing S s3 { }; // OK: invoke #2
--结束示例]
否则,我们有一个未推导的上下文