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

j8yoct9x  于 2023-07-01  发布在  其他
关注(0)|答案(2)|浏览(124)

我在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{};

所以我认为你不能迭代空的初始化器列表的原因是因为它不能推导出内部类型,因此不能首先构造它。
我想在这里澄清一下我的想法和推理

hgqdbh6s

hgqdbh6s1#

让我们从特殊情况开始:

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

在这种情况下,auto&&实际上不能推导出任何东西,因为初始化列表必须始终从使用它们的上下文(例如函数参数,复制初始化等)接收它们的类型。
然而,语言增加了一些 *“回退情况 *”,我们简单地将这样的初始化器列表视为std::initializer_list。上面的例子就是其中一种情况,z的类型为std::initializer_list<int>&&。也就是说,z不是转发引用,而是右值引用。我们知道它是std::initializer_list<int>,因为{1, 2, 3}中的所有表达式都是int

初始化器列表在for-loops中

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循环扩展到的内容。
请注意,x在这里是一个转发引用,与std::initializer_list无关。它总是这样,不管我们在迭代什么。
无论如何,: {1, 2, 3}可以工作,因为我们用它来初始化__range,就像在最初的z示例中一样。__range将是std::initializer_list的右值引用。

破箱

{{1}}

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

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

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

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

以同样的方式破碎。如果初始化器列表为空,我们就无法知道std::initializer_list应该是什么类型。

w8ntj3qf

w8ntj3qf2#

在这种情况下,您指定的类型为x,而不是initializers_list。如果initializer_list中的元素可以被转换为x,例如我可以使用char而不是int,如果我的类型支持这种转换,它将编译。所以int是变量x的类型。编译器没有得到任何关于initializer_list类型的线索。

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

要显式地告诉你的类型,你必须像这样告诉它的类型:

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

}

例如,下面的代码将打印abc

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

相关问题