范围循环用于空初始化列表

huangapple go评论75阅读模式
英文:

Range for loop for empty initializer list

问题

我在cpp参考文档 https://en.cppreference.com/w/cpp/language/reference#Forwarding_references 上阅读了有关转发引用的信息,并且对于存在一种特殊情况的转发引用感到很感兴趣:

auto&& z = {1, 2, 3}; // 不是 转发引用(用于初始化列表的特殊情况)

所以我开始在godbolt上进行实验(顺便说一句,我很想知道为什么需要这种特殊情况)。我略微惊讶地发现我可以像这样迭代初始化列表:

for (auto&& x : {1, 2, 3})
{
// 做一些事情
}

直到我意识到x被推导为int,因此以下内容不起作用:

for (auto&& x : {{1}})
{
// 做一些事情
}

所以我认为在这里,Auto不能推导初始化列表,因为上面提到的特殊情况?

然后我尝试了一个空列表,这也没有编译:

for (auto&& x : {})
{
// 做一些事情
}

使用GCC的编译器错误消息建议这是因为它无法从空列表中推导出auto,所以我尝试了以下内容:

for (int x : {})
{
// 做一些事情
}

为了明确告诉编译器它是一个类型为int的空列表。这让我感到惊讶,我期望由于我明确给出了类型,它可以推导出{}是什么,尤其是在迭代一个已填充的初始化列表时。经过一些实验,我发现以下行也无法编译:

auto x{};
所以我认为你不能迭代空的初始化列表的原因是它无法推导出内部类型,因此无法首先构造它。

我希望对我的思考和推理有一些明确的解释。

英文:

I was reading about forwarding references on cpp reference https://en.cppreference.com/w/cpp/language/reference#Forwarding_references and I was interested to learn that there is a special case for forwarding references:

auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)

So I started experimenting on godbolt (as a side note I'd be interested to know why this special case is needed). I was slightly surprised to find I could iterate over an initialiser list like so:

for (auto&& x : {1, 2, 3})
{
    // do something 
}

Until I realised that x was deduced as int and that therefore the following wouldn't work:

for (auto&& x : {{1}})
{
    // do something
}

So I think here, Auto couldn't deduce the initialiser list, because of the special case mentioned above?

Then I tried an empty list, which didn't compile also:

for (auto&& x : {})
{
    // do something
}

The compiler error message using GCC suggests that this is because it couldn't deduce auto from the empty list, so I then tried the following:

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

To explicitly tell the compiler that it's an empty list of type int. This surprised me, I expected that since I had explicitly given the type, it could deduce what {} was, especially since iterating over a populated version of the initialiser list worked. After some experimentation I found that the following line also does not compile:

auto x{};

So I think the reason you can't iterate over the empty initialiser list is because it cannot deduce the inner type and therefore can't construct it in the first place.

I would like some clarity on my thoughts and reasoning here

答案1

得分: 2

让我们从特殊情况开始:

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

在这种情况下,auto&& 实际上不能被推导为任何类型,因为初始化列表必须始终从它们所在的上下文中获取类型(例如函数参数、复制初始化等)。

然而,语言中添加了一些“备用情况”,在这些情况下,我们将这样的初始化列表简单地视为 std::initializer_list
上面的示例就是其中之一,z 的类型将是 std::initializer_list<int>&&
也就是说,z 不是一个转发引用,而是一个右值引用。
我们知道它是 std::initializer_list<int>,因为{1, 2, 3} 中的所有表达式都是 int

注意:术语“初始化列表”指的是语言构造{...}(如list initialization),它不一定是 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 循环展开的方式完全相同。

请注意,这里的 x 是一个转发引用,与std::initializer_list无关。无论我们遍历什么,它始终都是这样。

总之,:{1, 2, 3} 之所以起作用,是因为我们用它初始化了 __range,就像最初的示例中的 z 一样。
然后,__range 将成为对 std::initializer_list<int> 的右值引用。

无法工作的情况

{{1}}

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

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

这两种情况也无法工作,因为正如您在上面的展开中看到的那样,int 并没有提供关于 std::initializer_list 的类型的任何提示。
循环变量的类型在完全不同的地方使用,因此我们最终得到了在这两种情况下都不允许的 auto &&__range = {}

auto x = {};
// 或者
auto x{};

以相同的方式无法工作。对于空的初始化列表,我们无法知道 std::initializer_list 的类型应该是什么。

英文:

Let's start with the special case:

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

In this case, auto&amp;&amp; isn't really deducible to anything, because initializer lists must always receive their type from the context where they're used (such as function parameters, copy initialization, etc.

However, the language has added a few "fallback cases" where we simply treat such initializer lists as std::initializer_list.
The example above is one of those cases, and z will be of type std::initializer_list&lt;int&gt;&amp;&amp;.
I.e., z is not a forwarding reference, but an rvalue reference.
We know that it's std::initializer_list&lt;int&gt; because all of the expressions in {1, 2, 3} are int.

Note: the term "initializer list" refers to the language construct {...} (as in list initialization), which is not necessarily std::initializer_list.

Initializer lists in for-loops

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

We can make sense of what happens by expanding it:

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

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

This is exactly what range-based for loops expand to since C++20.

Note that x is a forwarding reference here, and that has nothing to do with std::initializer_list. It always is, regardless of what we're iterating over.

Anyhow, : {1, 2, 3} works because we initialize __range with it, just like in the original example with z.
__range will then be an rvalue reference to a std::initializer_list<int>.

Broken cases

{{1}}

doesn't compile because the inner {1} cannot deduce what is being initialized using the braces here.
This is not one of those cases where we can fall back onto std::initializer_list.

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

These two also don't work, because as you've seen in the expansion above, the int isn't giving any hint as to what type the std::initializer_list should be.
The type of the loop variable is being used in an entirely different place, so we end up with auto &amp;&amp;__range = {} in both cases, which is not allowed.

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

Are broken in the same way. With an empty initializer list, we have no way of knowing what type the std::initializer_list should be.

答案2

得分: 0

对于这种情况,你是在指定 x 的类型,而不是在指定初始化器列表的类型。如果初始化器列表中的元素可以转换为 x 的类型,编译器会编译通过,例如,我可以使用 char 代替 int,如果我的类型支持此转换,它将编译通过。所以,int 是变量 x 的类型。编译器无法推断出初始化器列表的类型。

for (int x : {})
{
    // 做一些事情
}

要明确告诉编译器你的类型,你需要像这样指定它的类型:

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

例如,以下代码将打印 abc

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

For the case, you are specifying the type of x not for the initializers_list. It will compile if element in initializer_list can be converted x for example I can use char instead of int it will compile if my types supports this conversion. So int is the type of the variable x. Compiler is not getting any clue of the type of initializer_list.

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

To explicitly tell about your type you have to tell its type like this:

for(auto i: std::initializer_list&lt;int&gt;{})
{

}

For example this code will print abc:

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

huangapple
  • 本文由 发表于 2023年6月27日 18:15:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76563851.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定