Proper style for declaration in range-based for

后端 未结 1 1770
囚心锁ツ
囚心锁ツ 2020-12-31 09:31

This question mentioned the obvious, idiomatic usage of C++11 range-based for.

for (auto& elem: container) {
  // do something with elem
}
相关标签:
1条回答
  • 2020-12-31 10:19

    First, some general advice on how to use auto that is not specific to range-for. auto&& can be problematic if the initializer is an xvalue referring to a temporary, since lifetime extension may not be applied in this case. To put it more simply, and with code:

    // Pass-through identity function that doesn't construct objects
    template<typename T>
    T&&
    id(T&& t)
    { return std::forward<T>(t); }
    
    // Ok, lifetime extended
    // T {} is a prvalue
    auto&& i = T {};
    
    T* address = &i;
    
    // Still ok: lifetime of the object referred to by i exceed that of j
    // id(whatever) is an xvalue
    auto&& j = id(std::move(i));
    
    // No other object is involved or were constructed,
    // all those references are bound to the same object
    assert( &j == address );
    
    // Oops, temporary expires at semi-colon
    // id(whatever) is an xvalue, again
    auto&& k = id(T {});
    

    The big clue that there's something shady going on here is that id has return type T&&. If it returned T then id(whatever) would be a prvalue, and the returned temporary would have had its lifetime extended (however that would involve a construction).


    With that out of the way, when it comes to range-for though you have to remember that for(auto&& ref: init) { /* body */ } is specified to be roughly equivalent to the following (ignoring some details that don't matter here):

    {
        using std::begin;
        using std::end;
        auto&& range = init;
        for(auto b = begin(range), e = end(range); b != e; ++b) {
            auto&& ref = *b;
            /* body */
        }
    }
    

    We need to ask ourselves now, what if *b is an xvalue (i.e. the iterator type has an operator* returning value_type&&, as is the case e.g. with std::move_iterator<Iterator>)? It must then refer to an object that will outlive ref, since the line auto&& ref = *b; involves no temporary. Hence it's safe. Otherwise, if *b is a prvalue (i.e. the iterator type has an operator* returning T for some object type T), then the lifetime of the temporary gets extended for the rest of the loop body. In all cases you're safe (the case where *b is an lvalue being left as an exercise to the reader).

    I personally make heavy use of auto&&, with or without range-for. But I do ask myself every time whether the initializer is an xvalue or not, and if it is, what is the lifetime of what is being referred to.

    0 讨论(0)
提交回复
热议问题