Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

temporary lifetime in range-for expression

Tags:

Consider a simple class A that can be used as a range:

struct A {      ~A() { std::cout << "~A "; }      const char* begin() const {         std::cout << "A::begin ";         return s.data();     }         const char* end() const {         std::cout << "A::end ";         return s.data() + s.size();     }         std::string s; }; 

If I make a temporary A in a range-for, it works exactly as I would hope:

for (auto c : A{"works"}) {     std::cout << c << ' '; }   // output A::begin A::end w o r k s ~A  

However, if I try to wrap the temporary:

struct wrap {     wrap(A&& a) : a(std::move(a))     { }       const char* begin() const { return a.begin(); }     const char* end() const { return a.end(); }      A&& a; };  for (auto c : wrap(A{"fails"})) {     std::cout << c << ' '; }  // The temporary A gets destroyed before the loop even begins:  ~A A::begin A::end  ^^ 

Why is A's lifetime not extended for the full range-for expression, and how can I make that happen without resorting to making a copy of the A?

like image 494
Barry Avatar asked May 01 '15 15:05

Barry


1 Answers

The reason the lifetime of the temporary is not extended is how the standard defines range-based for loops in

6.5.4 The range-based for statement [stmt.ranged]

1 For a range-based for statement of the form

for (for-range-declaration:expression)statement

let range-init be equivalent to the expression surrounded by parentheses

( expression )

and for a range-based for statement of the form

for (for-range-declaration:braced-init-list)statement

let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to

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

Note that auto && __range = range-init; would extend the lifetime of a temporary returned from range-init, but it does not extend the lifetime of nested temporaries inside of range-init.

This is IMHO a very unfortunate definition and was even discussed as Defect Report 900. 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.

The solution is to store a copy in the wrapper - which often defeats the purpose of the wrapper.

like image 135
Daniel Frey Avatar answered Oct 15 '22 12:10

Daniel Frey