Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why it is legal here to create lvalue reference to prvalue?

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?

like image 837
SwiftMango Avatar asked Jan 30 '14 19:01

SwiftMango


4 Answers

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!

like image 189
templatetypedef Avatar answered Sep 23 '22 09:09

templatetypedef


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.

like image 20
Angew is no longer proud of SO Avatar answered Sep 24 '22 09:09

Angew is no longer proud of SO


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.

like image 6
K-ballo Avatar answered Sep 22 '22 09:09

K-ballo


The vector may be a prvalue, but the ints 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 ints 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.

like image 1
jrok Avatar answered Sep 23 '22 09:09

jrok