I have code below:
#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;
}
As far as I understand, the vector is prvalue, and cannot bind to int&
, but this one works correctly?
Is it because for range loop is simply macro expansion and a temporary variable would be created for the vector?
According to the C++ ISO spec, the range-based for loop is formally defined to be equivalent to
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
Note that "equivalent to" doesn't mean "expands out to as a macro," but rather "has the exact same behavior as". This equivalence isn't a macro in the traditional sense (i.e. it's not a #define
), but rather is given in the ISO spec as a formal way of defining the semantics of what the range-based for loop does. The implementation of the range-based for loop can be whatever the compiler likes, as long as the behavior is exactly identical to the behavior given above.
In this context, range_expression
is a prvalue, but it's then bound to __range
, which is an lvalue. The iterators obtained that scan over __range
are also lvalues, and so when the iterators are dereferenced to yield the individual values in the range, those values will be lvalues.
Hope this helps!
The range-based for
loop is most definitely not macro expansion. It's a separate construct of the language. Still, while the vector itself is a prvalue, its member functions still operate normally. So its operator[]
(or dereferencing its iterator) returns a normal lvalue reference etc.
Of course, such references are only valid as long as the vector itself exists. Its lifetime lasts for the entire range-based for
loop (that is mandated by the range-based for
loop specification in the standard), so all is well.
As far as value categories are concerned, it's the same as this (which is also legal):
int &i = std::vector<int>{1, 2, 3}[0];
Of course, unlike the one in the range-based for
loop, this i
reference become dangling immediately. But the principle is the same.
Consider also this: the language has no way of knowing that the lvalue reference returned by the iterator's operator *
or the vector's operator[]
refers to something whose lifetime is bound to that of the vector. It simply returns an lvalue reference, so it's bindable.
The range-based for-loop is not macro expansion but a true language feature. It does extend the lifetime of the temporary to the entire for body.
The vector may be a prvalue, but the int
s within are lvalues.
the vector is prvalue, and cannot bind to int&
A vector can't bind to int&
, regardless of its value category :) The int
s within the vector are lvalues, though, and can bind to int&
.
Is it because for range loop is simply macro expansion...
Range for is not a macro expansion. It's a first class language construct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With