Lifetime of temporary object associated with const reference (method chaining)

When you write a function thus...

const S& f(int i) const { std::cout << i << "\n"; return *this; }

...you're instructing the compiler to return a const S& and you are taking responsibility for ensuring the referenced object has a lifetime suitable for the caller's use. ("ensuring" may constitute documenting client usage that works properly with your design.)

Often - with typical separation of code into headers and implementation files - f(int) const's implementation won't even be visible to calling code, and in such cases the compiler has no insight regarding to which S a reference might be returned, nor whether that S is a temporary or not, so it has no basis on which to decide whether the lifetime needs to be extended.

As well as the obvious options (e.g. trusting clients to write safe code, returning by value or smart pointer), it's worth knowing about a more obscure option...

const S& f(int i) const & { ...; return *this; }
const S f(int i) const && { ...; return *this; }

The & and && immediately before the function bodies overload f such that the && version is used if *this is movable, otherwise the & version is used. That way, someone binding a const & to f(...) called on an expiring object will bind to a new copy of the object and have the lifetime extended per the local const reference, while when the object isn't expiring (yet) the const reference will be to the original object (which still isn't guaranteed live as long as the reference - some caution needed).


Why did this happen? Is f()'s return value not a correct type of "temporality"?

Right, it's not. This is a somewhat controversial issue recently: the official definition of "temporality" is somewhat open-ended.

In recent compilers, temporality has been expanding. First it only applied to prvalue (non-"reference") expressions, and member accesses ("dot operator") applied to such expressions. Now it applies to cast expressions and array accesses as well. Although you can write a move operation as static_cast< T && >( t ), which will preserve temporality, simply writing std::move( t ) will not.

I'm working on a series of proposals to extend C++ so your example will work as you expected. There's some nonzero chance that the feature could appear in C++17.