std::initializer_list,花括号初始化和头文件

14

在阅读其他主题时,我遇到了一种奇怪的行为,至少对我来说是这样。 这个想法源于 auto 和大括号之间的特殊交互。如果你写下以下代码:

auto A = { 1, 2, 3 }

编译器会将A 推导为std::initializer_list。奇怪的是,类似的规则不仅适用于auto,其中可能会有特殊原因,而且也适用于其他情况。

如果你写下以下内容:

template<typename T>
void f(std::vector<T> Vector)
{
    // do something
}

当然你不能这样调用:

f({ 1, 2, 3});

尽管可以使用花括号初始化std::vector,但如果您将std::vector替换为std::initializer_list,则调用将起作用,并且编译器将正确推断T类型为int。更有趣的是,在前一种情况下,您需要包括#include <vector>,而在后一种情况下,您不需要#include <initializer_list>。这让我思考,经过测试后,我发现某种程度上std::initializer_list不需要自己的头文件,因此它在某种程度上是“基本”功能的一部分。
此外,为了使一切都有意义,std::initializer_list应该以更或多或少相同的方式将标准对象与可调用对象(在最严格的意义上,即具有operator()的对象)联系起来。换句话说,未命名的花括号定义应默认为std::initializer_list,就像lambda(大多数情况下)是未命名的可调用对象一样。
这种推理正确吗?此外,这种行为是否可以更改,如果可以,如何更改?
更新:发现从iostream中中转地包含了initializer_list的头文件(非常奇怪)。但是,问题仍然存在:为什么对于std::initializer_list调用有效,而对于std::vector不起作用?

1
使用初始化列表的程序,但不包括<initializer_list>头文件是不完整的。例如,GCC会报错:无法推断初始化列表的类型,因为未找到std::initializer_list;请包含<initializer_list> - Evg
你的函数问题在于它是一个模板,它将模板参数T推断为initializer_list,而vector不能容纳它。如果你只是写成了void f(std::vector<int> v),那么调用f({1,2,3});就可以正常工作了。我记得Scott Myers曾经谈到过initializer_list以及它与autovector之间的奇怪交互,但我需要看看是否能找到相关内容。 - Tas
3个回答

9
如果我们使用std::initializer_list,却没有包含initializer_list头文件,则其形式不正确(因此需要进行诊断)。我们可以从[dcl.init.list]p2中看到:

……模板std::initializer_list未预定义;如果在使用std::initializer_list之前未包含头文件<initializer_list>——即使是未命名类型的隐式使用(9.1.7.4)——则程序形式不正确。

大多数情况下,您可能会传递地包含头文件,这是形式正确的,但会使代码更加脆弱,因此请包含您使用的内容。

我们可以从现场godbolt示例中看到,如果没有包含任何内容,我们将获得所需的诊断信息,例如gcc/clang/MSVC

error: use of undeclared identifier 'std'    
void foo( std::initializer_list<int>) {
          ^

我需要翻译的内容是:

包括<vector><iostream>,我们不再获得诊断

[temp.deduct.type]p5解释了为什么它不能像您期望的那样推导出来,告诉我们这是一个非推导上下文:

The non-deduced contexts are:
...
- A function parameter for which the associated argument is an initializer list ([dcl.init.list]) but the parameter does not have a type for which deduction from an initializer list is specified ([temp.deduct.call]).> [ Example:

template<class T> void g(T);
g({1,2,3});                 // error: no argument deduced for T

— end example  ]
...

请参阅[temp.deduct.call]p1:

... 否则,初始化器列表参数将导致将参数视为不能推断的上下文([temp.deduct.type])...


这是一种可能性,但我只包含了iostream和VS项目中的内置头文件,不应该包含任何其他间接内容。 - Andrea Bocco
@AndreaBocco 是的,包括 iostreamvector,看这个 live godbolt ... 如果你注释掉这些包含语句,你会得到一个诊断信息。 - Shafik Yaghmour
是的,您说得没错,头文件会被传递包含。不过,问题的另一“半部分”仍然存在:为什么将花括号初始化器传递给向量函数时不起作用,而对于初始化列表确实可以起作用? - Andrea Bocco
@AndreaBocco 抱歉,之前没有清楚地看到有两个问题,我已经更新回答了。 - Shafik Yaghmour

1

你可能是从<vector><iostream>中传递地包含头文件,需要注意的是,标准明确强制执行非推导上下文对于std::vector情况。

[temp.deduct.type]/p5

不推导的上下文包括:

...

  • 一个函数参数,其相关联的参数是初始化列表([dcl.init.list]),但该参数没有指定从初始化列表进行推导的类型([temp.deduct.call])。

参见cppreference ex.6


0

C++的在线参考文档<vector>显示,<initializer_list>已经包含在其头文件中。

GCC实现的<vector>包括<initializer_list>。其他实现也可能是如此。这就是为什么您不必单独包含<initializer_list>的原因。

请查看GCC 4.6.2的源代码,其中包括<initializer_list><vector>头文件中。 https://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a01069_source.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接