空初始值设定项列表的 for 循环范围

Range for loop for empty initializer list

提问人:HarryP2023 提问时间:6/27/2023 更新时间:6/27/2023 访问量:171

问:

我在 cpp 参考文献 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{};

所以我认为你不能遍历空的初始值设定项列表的原因是因为它无法推断内部类型,因此不能首先构造它。

我想在这里澄清一下我的想法和推理

C++ 初始值设定项列表 完美转发基于 范围的循环

评论

1赞 digito_evo 6/27/2023
std::initializer_list是具有类型模板参数的类模板。编译器需要知道该类型是什么才能实例化它。现在,在 的情况下,编译器应该如何知道要使用的专用化是什么?Tauto x{};std::initializer_list<T>
0赞 HarryP2023 6/27/2023
@digito_evo 的情况下,这是有道理的。但从理论上讲,编译器可以在 的情况下推导出 T,因为我已经说过 x 的类型是 ?auto x{};for (int x : {})int

答:

0赞 foragerDev 6/27/2023 #1

对于这种情况,您要为initializers_list指定 not 的类型。如果 initializer_list 中的元素可以转换 x,它将编译,例如,如果我的类型支持这种转换,我可以使用它来代替它。变量 x 的类型也是如此。编译器没有获得任何有关initializer_list类型的线索。xcharintint

for (int x : {})
{
    // do something
}

要明确地告诉您的类型,您必须像这样告诉其类型:

for(auto i: std::initializer_list<int>{})
{

}

例如,此代码将打印:abc

for(char i: {97, 98, 99}) 
{
    std::cout << i;
}

评论

1赞 463035818_is_not_an_ai 6/27/2023
此处列出了初始值设定项列表存在的 FWIW 案例: en.cppreference.com/w/cpp/utility/initializer_list
2赞 Jan Schultke 6/27/2023 #2

让我们从特殊情况开始:

auto&& z = {1, 2, 3};

在这种情况下,并不能真正推断为任何东西,因为初始值设定项列表必须始终从使用它们的上下文(例如函数参数、复制初始化等)接收其类型。auto&&

但是,该语言添加了一些“后备案例”,我们只是将此类初始值设定项列表视为 std::initializer_list。 上面的示例就是其中一种情况,其类型为 。 即,不是转发引用,而是右值引用。 我们知道这是因为 中的所有表达式都是 .zstd::initializer_list<int>&&zstd::initializer_list<int>{1, 2, 3}int

注意:术语“初始值设定项列表”是指语言结构 {...}(如列表初始化),它不一定是 std::initializer_list

for 循环中的初始值设定项列表

for (auto&& x : {1, 2, 3}) { /* ... */ }

我们可以通过扩展它来理解会发生什么:

/* init-statement */
auto &&__range = {1, 2, 3};
auto __begin = begin(__range)
auto __end = end(__range);

for ( ; __begin != __end; ++__begin) {
    auto&& x = *begin;
    /* ... */
}

这正是自 C++20 以来基于范围的 for 循环扩展到的

请注意,此处是转发引用,与 无关。无论我们迭代什么,它总是如此。xstd::initializer_list

无论如何,之所以有效,是因为我们用它进行初始化,就像在原始示例中使用 . 然后,将是对 .: {1, 2, 3}__rangez__rangestd::initializer_list

破碎的案例

{{1}}

不编译,因为内部无法推断出使用此处的大括号初始化的内容。 这不是我们可以回退到的情况之一。{1}std::initializer_list

for (auto x : {})
// and
for (int x : {})

这两个也不起作用,因为正如您在上面的扩展中看到的那样,它没有给出任何关于应该是什么类型的提示。 循环变量的类型被用于完全不同的地方,所以我们最终在这两种情况下都得到了,这是不允许的。intstd::initializer_listauto &&__range = {}

auto x = {};
// or
auto x{};

以同样的方式被打破。对于空的初始值设定项列表,我们无法知道应该是什么类型。std::initializer_list