我在cpp reference www.example.com上阅读了关于转发引用的内容https://en.cppreference.com/w/cpp/language/reference#Forwarding_references,我很感兴趣地了解到转发引用有一个特殊的情况:
auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)
所以我开始在godbolt上做实验(顺便说一句,我很想知道为什么需要这个特例)。我有点惊讶地发现我可以像这样迭代初始化器列表:
for (auto&& x : {1, 2, 3})
{
// do something
}
直到我意识到x被推导为int
,因此下面的公式不起作用:
for (auto&& x : {{1}})
{
// do something
}
所以我认为在这里,Auto不能推导出初始化器列表,因为上面提到的特殊情况?
然后我尝试了一个空列表,它也没有编译:
for (auto&& x : {})
{
// do something
}
使用GCC的编译器错误消息表明,这是因为它无法从空列表中推导出auto,所以我尝试了以下方法:
for (int x : {})
{
// do something
}
显式地告诉编译器它是一个int
类型的空列表。这让我很惊讶,我以为既然我已经显式地给出了类型,它就可以推断出{}是什么,特别是因为迭代初始化器列表的填充版本是有效的。经过一些实验,我发现下面这行代码也无法编译:
auto x{};
所以我认为你不能迭代空的初始化器列表的原因是因为它不能推导出内部类型,因此不能首先构造它。
我想在这里澄清一下我的想法和推理
2条答案
按热度按时间hgqdbh6s1#
让我们从特殊情况开始:
在这种情况下,
auto&&
实际上不能推导出任何东西,因为初始化列表必须始终从使用它们的上下文(例如函数参数,复制初始化等)接收它们的类型。然而,语言增加了一些 *“回退情况 *”,我们简单地将这样的初始化器列表视为
std::initializer_list
。上面的例子就是其中一种情况,z
的类型为std::initializer_list<int>&&
。也就是说,z
不是转发引用,而是右值引用。我们知道它是std::initializer_list<int>
,因为{1, 2, 3}
中的所有表达式都是int
。{...}
(如list initialization),不一定是std::initializer_list
。初始化器列表在for-loops中
我们可以通过扩展它来理解所发生的事情:
这正是自C++20以来基于范围的for循环扩展到的内容。
请注意,
x
在这里是一个转发引用,与std::initializer_list
无关。它总是这样,不管我们在迭代什么。无论如何,
: {1, 2, 3}
可以工作,因为我们用它来初始化__range
,就像在最初的z
示例中一样。__range
将是std::initializer_list
的右值引用。破箱
无法编译,因为内部
{1}
无法推断出使用此处的大括号初始化的内容。这不是我们可以退回到std::initializer_list
的那些情况之一。这两个也不起作用,因为正如您在上面的扩展中所看到的,
int
没有给出任何关于std::initializer_list
应该是什么类型的提示。循环变量的类型在完全不同的地方使用,所以我们在两种情况下都使用了auto &&__range = {}
,这是不允许的。以同样的方式破碎。如果初始化器列表为空,我们就无法知道
std::initializer_list
应该是什么类型。w8ntj3qf2#
在这种情况下,您指定的类型为
x
,而不是initializers_list。如果initializer_list中的元素可以被转换为x,例如我可以使用char
而不是int
,如果我的类型支持这种转换,它将编译。所以int
是变量x的类型。编译器没有得到任何关于initializer_list类型的线索。要显式地告诉你的类型,你必须像这样告诉它的类型:
例如,下面的代码将打印
abc
: