关于 c :Braced-init-lists 和函数模板类型的推演顺序

Braced-init-lists and function template type deduction order

我有一个关于函数模板参数类型推导程序的问题。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
#include <vector>
#include <sstream>
#include <string>
#include <iterator>
#include <fstream>

int main()
{
    std::ifstream file("path/to/file");
    std::vector<int> vec(std::istream_iterator<int>{file},{}); // <- This part
    return 0;
}

如果我理解正确,第二个参数被推断为 std::istream_iterator 类型,其中调用了默认构造函数。

适当的 std::vector 构造函数声明为:

1
2
3
template <class InputIterator>
         vector (InputIterator first, InputIterator last,
                 const allocator_type& alloc = allocator_type());

由于第一个参数类型被推导出为 std::istream_iterator<int>,第二个参数也被推导出为 std::istream_iterator<int>,因此可以应用统一的初始化语义。我不知道的是类型推导发生的顺序。我真的很感激有关这方面的一些信息。

提前致谢!


让我们用一个更简单的例子:

1
2
3
4
template<class T>
void foo(T, T);

foo(42, {});

函数调用有两个参数:

  • int 类型的纯右值表达式(整数文字)
  • 一个花括号初始化列表 {}

后者,{},可以是表达式列表的一部分,但它本身不是表达式。表达式列表被定义为初始化列表。大括号初始化列表没有类型。

对每个函数参数单独进行模板类型扣除[temp.deduct.type]/2。 [temp.deduct.call]/1 说明函数参数 P:

的类型推导

If removing references and cv-qualifiers from P gives
std::initializer_list<P'> for some P' and the argument is an
initializer list, then deduction is performed instead for each element
of the initializer list, taking P' as a function template parameter
type and the initializer element as its argument. Otherwise, an
initializer list argument causes the parameter to be considered a
non-deduced context. [emphasis mine]

所以在调用 foo(42, {}); 中,不会从第二个参数 {} 推导出 T。然而,T 可以从第一个参数推导出来。

一般来说,我们可以从多个函数参数中推导出T。在这种情况下,推导的类型必须完全匹配 [temp.deduct.type]/2。如果类型仅从一个函数参数推导出但在其他地方使用(在另一个函数参数中,在非推导上下文中,在返回类型等中),则没有问题。类型扣除可能会失败,例如当模板参数不能从任何函数参数中推导出来且没有显式设置时。

推演后,T 将被 int 替代,产生类似于:

的函数签名

1
void foo<int>(int, int);

可以使用两个参数42{} 调用此函数。后者将执行复制列表初始化,导致第二个参数的值初始化。