What's the lifetime of temporary objects in a range-for?

Viewed 189

Consider this class:

class Foo
{
public:

    ~ Foo ()
    {
        std::cout << "~Foo\n";
    }

    typedef std::vector<std::string> Words;

    const Words & words ()
    {
        return m_words;
    }

private:

    Words m_words = {"foo", "bar", "baz"};
};

Section 12.2 of the C++ standard specifies lifetimes of temporary objects. I thought this would be okay:

for (auto w : Foo () .words ())
    std::cout << w << "\n";

But it wasn't

~Foo
terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_M_construct null not valid
[1]    10290 abort (core dumped)  ./a.out

The standard is confusing me. Why is ~Foo being called before the loop runs?

2 Answers

The current standard says in The range-based for statement [stmt.ranged] that

The range-based for statement
for ( init-statementopt for-range-declaration : for-range-initializer ) statement
is equivalent to

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

This means that your Foo().words() is only used in the assignment auto &&__range = Foo().words(); and that the temporary object not lives until the code reaches the for loop.

Please note that I copied from the latest C++20 draft. In C++11 the code is a bit different, but the relevant part is the same.

I think that the answer is to be found in the way the range-for is defined and to what __range is bound to.

[class.temporary]/6 - There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression.

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

If you change the for-loop expression to bind directly to the subobject m_words(assuming that it were public), then the lifetime of Foo() will be extended and the following will work

for (auto w : Foo ().m_words)
    std::cout << w << "\n";
Related