扣除指南,initializer_list和类型扣除过程

问题描述 投票:2回答:2

考虑一下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++ language-lawyer c++17 initializer-list list-initialization
2个回答
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
投票

根据您的扣除指南,我们最终得到的相当于:

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

© www.soinside.com 2019 - 2024. All rights reserved.