考虑一下following code:
#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 t{{1, 2}};
而不是test<int> t{{1, 2}};
。
test<int>
有一个构造函数,它接受一个pair<int, int>
,它可以匹配braced-init-list中的值,这样就可以调用它。
这种事情的部分原因是允许聚合参与类模板参数推导。聚合没有用户提供的构造函数,因此如果演绎指南仅限于实际构造函数,则无法使聚合工作。
所以我们得到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.match.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
- 结束例子]
否则我们有一个non-deduced context