Question

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

for (auto& elem: container) {
  // do something with elem
}

I've been having doubts about the kind of reference you're supposed to use, though. Input iterators may return rvalues. Although the implicit type introduced by auto could be deduced to const which would bind to an rvalue, that doesn't seem to occur.

Is the best general practice to use perfect forwarding?

for (auto && elem: container) {
  // do something with elem
}

I see no downside here, but it looks a little too cute. Maybe I still just haven't written enough C++11.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top