推导,initializer_list和类型推导过程

时间:2018-11-12 19:36:13

标签: c++ language-lawyer c++17 initializer-list list-initialization

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

在这里,逐步发生了什么?

2 个答案:

答案 0 :(得分:2)

之所以进行编译,是因为这就是类模板推导指南的工作方式。

推论指南是假设类型的构造函数。它们确实不存在。它们的唯一目的是确定如何推断类模板参数。

进行推论后,实际的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可以保持聚合。

答案 1 :(得分:1)

根据您的推论指南,我们得出的结论等同于:

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
     

-示例example] [示例:

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

-示例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
     

-示例example]

否则,我们会有一个non-deduced context