简体   繁体   中英

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?

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 (ie 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.

As far as I understand, the vector is prvalue, and cannot bind to int&, but this one works correctly?

The error I believe you've made is that you're attributing value categories to objects rather than to expressions; You reason that because the vector object is a prvalue then so are its elements, and thus int &v can't be bound to those prvalues.

In fact value categories are an attribute of expressions rather than objects: As such it doesn't even make sense to reason about the elements of the range as having a value category and determining if the variable int &v is allowed to bind to those objects.

So while it's true that the expression that creates the vector is a prvalue, the created vector is just a regular object and its elements are just regular objects, all perfectly valid for referencing from a non-const lvalue reference typed variable.

Instead the intuitive explanation of the range-based for loop gives the correct answer: The variable gets initialized with each element of the range in turn and it just works.


Also, value categories are checked at compile time. If the code were actually illegal for the reason you give then you would get a compile error such as the following:

error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
   int &v = 10;
        ^   ~~

The undefined behavior you might expect to encounter at runtime has nothing to do with value categories: You might expect that the temporary vector's lifetime ends immediately after the range expression is evaluated, and thus the loop variable is a dangling reference into a vector that's been destroyed by the time the loop body first starts executing.

In fact the temporary vector has its lifetime extended for the entire duration of the loop, so there's no problem with dangling references on that account.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM