我的代码如下:
#include <vector>
#include <iostream>
int main(){
for(int& v : std::vector<int>{1,3,5,10}) {
std::cout << v << std::endl;
v++; // Does this cause undefined behavior?
}
return 0;
}
据我了解,该向量是prvalue,无法绑定到int&
,但这个可以正常工作吗?
是因为范围循环只是宏扩展而且会为向量创建一个临时变量吗?
答案 0 :(得分:17)
根据C ++ ISO规范,基于范围的for循环被正式定义为等同于
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
请注意,“等同于”并不意味着“扩展为宏”,而是“具有与”完全相同的行为“。这种等价不是传统意义上的宏(即它不是#define
),而是在ISO规范中给出,作为定义基于范围的for循环的语义的形式方式。基于范围的for循环的实现可以是编译器喜欢的任何内容,只要该行为与上面给出的行为完全相同。
在这种情况下,range_expression
是一个prvalue,但它然后绑定到__range
,这是一个左值。在__range
上扫描获得的迭代器也是左值,因此当取消引用迭代器以产生范围内的各个值时,这些值将是左值。
希望这有帮助!
答案 1 :(得分:9)
基于范围的for
循环绝对是不宏扩展。它是该语言的独立构造。尽管矢量本身是一个prvalue,但它的成员函数仍然正常运行。所以它的operator[]
(或解引用它的迭代器)返回一个正常的左值引用等。
当然,只要载体本身存在,这些引用才有效。它的生命周期持续整个基于范围的for
循环(由标准中基于范围的for
循环规范强制执行),所以一切都很好。
就价值类别而言,它与此相同(也是legal):
int &i = std::vector<int>{1, 2, 3}[0];
当然,与基于范围的for
循环中的那个不同,此i
引用会立即悬空。但原则是一样的。
还要考虑这一点:语言无法知道迭代器的operator *
或向量的operator[]
返回的左值引用是指其生命周期与向量的生命周期绑定的内容。它只返回一个左值引用,因此它是可绑定的。
答案 2 :(得分:6)
基于范围的for循环不是宏扩展,而是真正的语言功能。它确实将临时的寿命延长到整个身体。
答案 3 :(得分:1)
向量可能是prvalue,但int
内的是lvalues。
向量是prvalue,不能绑定到int&amp;
向量无法绑定到int&
,无论其值类别如何:)向量中的int
都是左值,并且可以绑定到int&
。
是因为范围循环只是宏扩展......
的范围不是宏扩展。它是一流的语言结构。
答案 4 :(得分:1)
据我所知,矢量是prvalue,不能绑定到int&amp;,但是这个可以正常工作吗?
我相信你犯的错误是你将价值类别归因于对象而不是表达;你的理由是因为vector对象是prvalue,因此它的元素也是如此,因此int &v
不能绑定到那些prvalues。
实际上,值类别是表达式而不是对象的属性:因此,将范围的元素推理为具有值类别并确定是否允许变量int &v
甚至没有意义绑定到那些对象。
因此,虽然创建向量的表达式是prvalue,但创建的向量只是一个常规对象,其元素只是常规对象,所有这些对于从非const左值引用类型变量引用都非常有效。 / p>
相反,对基于范围的for循环的直观解释给出了正确的答案:变量依次对该范围的每个元素进行初始化,它只是起作用。
此外,在编译时检查值类别。如果代码实际上是非法的,那么你会得到一个编译错误,如下所示:
error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
int &v = 10;
^ ~~
您可能期望在运行时遇到的未定义行为与值类别无关:您可能期望临时向量的生命周期在求值范围表达式之后立即结束,因此循环变量是对向量的悬空引用这是在循环体首次开始执行时被破坏的。
实际上临时向量的生命周期在整个循环期间都有扩展,因此对该帐户的悬空引用没有问题。