37

Suppose I have a function that returns a std::vector by value:

std::vector<int> buildVector();

It would seem natural to iterate over the result using a range-based for:

for (int i : buildVector()) {
  // ...
}

Question: Is it safe to do so?

My reading of the standard (actually, draft n4431) suggests that it might not be, though I'm having a hard time believing that the committee failed to allow for this usage. I'm hoping that my reading is incorrect.

Section 6.5.4 defines the range-based for:

for ( for-range-declaration : expression ) statement

with the following desugaring:

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
             __end = end-expr;
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

where range-init is just ( expression ), and at least for class types, begin-expr is either __range.begin() or begin(__range), etc.

In my buildVector example, I think the range-init produces a temporary, which the implementation is allowed to destroy immediately after the __range reference is bound. This would mean that the __range reference might already be dangling by the time begin-expr is evaluated.

Certainly, it should always be safe to write this:

std::vector<int> notATemporary = buildVector();
for (int i : notATemporary) {
  // ...
}

But I'm hoping I don't have to add this to my list of gotchas.

mbrcknl
  • 685
  • 6
  • 10

1 Answers1

28

Yes, it's perfectly safe.

From [class.temporary]/4-5:

There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when a default constructor is called [...]

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary bound to a reference member in a constructor’s ctor-initializer [...]
  • A temporary bound to a reference parameter in a function call [...]
  • The lifetime of a temporary bound to the returned value in a function return statement [...]
  • A temporary bound to a reference in a new-initializer [...]

None of those exceptions apply. The temporary thus persists for the lifetime of the reference, __range, which is the entire loop.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 6
    An important gotcha that will bite you if you start playing with ranges eventually: if you write a range adapter, and it is constructed from an rvalue, and you want to be able to chain it in a `for(:)` expression, you need to store your input range *by value*, because reference lifetime extensions is not transitive. Take `R&&`, store `R`. – Yakk - Adam Nevraumont May 26 '15 at 01:57
  • 1
    @Yakk Yep, even though [I tried otherwise](http://stackoverflow.com/questions/29990045/temporary-lifetime-in-range-for-expression). In fact, you even answered that one :) – Barry May 26 '15 at 02:02
  • @yakk Indeed, that was exactly the situation that got me thinking about this. – mbrcknl May 26 '15 at 02:14
  • 1
    Just be careful if you like boost range adaptors, since: `for (int i : buildVector() | boost::adaptors::reversed)` is not safe – Llopeth Feb 12 '18 at 17:58
  • Based on [this other answer](https://stackoverflow.com/a/29990971/10301017) I wonder if the justification given here is correct? Range-based for loop seems to have its own rules when it comes to reference liftime extension. In particular: "It seems to be the only part of the standard where a reference is implicitly bound to extend the lifetime of an expressions result without extending the lifetime of nested temporaries." – DancingIceCream Aug 14 '22 at 17:41