推导指南、initializer_list和类型推导过程



考虑以下代码:

#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

--结束示例]

否则,我们有一个未推导的上下文

最新更新