推导指南,initializer_list,以及类型推导过程

Deduction guides, initializer_list, and the type deduction process

考虑 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 的列表初始化。

这里到底发生了什么,一步一步?

它可以编译,因为这就是 class 模板推导指南的工作方式。

演绎指南是该类型的假设构造函数。它们并不真正存在。它们的唯一目的是确定如何推导出 class 模板参数。

进行推导后,实际的 C++ 代码将接管 test 的特定实例。因此,编译器的行为就好像您说的是 test<int> t{{1, 2}};.

而不是 test t{{1, 2}};

test<int> 有一个采用 pair<int, int> 的构造函数,它可以匹配 braced-init-list 中的值,所以这就是被调用的内容。

做这种事情的部分原因是为了让聚合参与 class 模板参数推导。聚合没有用户提供的构造函数,因此如果推导指南仅限于真正的构造函数,则聚合无法工作。

所以我们得到 class 模板推导指南 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 节说:

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed. [ Example:

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

— end example  ] [ Example:

struct Map {
  Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};

— end example  ] [ Example:

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

— end example  ]

否则我们有一个non-deduced context