In C++, the lifetime of a temporary value can be extended by binding it to a reference:
Foo make_foo();
{
Foo const & r1 = make_foo();
Foo && r2 = make_foo();
// ...
} // both objects are destroyed here
Why is this allowed? What problem does this solve?
I couldn't find an explanation for this in Design and Evolution (e.g. 6.3.2: Lifetime of Temporaries). Nor could I find any previous questions about this (this one came closest).
This feature is somewhat unintuitive and has subtle failure modes. For example:
Foo const & id(Foo const & x) { return x; } // looks like a fine function...
Foo const & r3 = id(make_foo()); // ... but causes a terrible error!
Why is something that can be so easily and silently abused part of the language?
Update: the point may be subtle enough to warrant some clarification: I do not dispute the use of the rule that "references bind to temporaries". That is all fine and well, and allows us to use implicit conversions when binding to references. What I am asking about is why the lifetime of the temporary is affected. To play the devil's advocate, I could claim that the existing rules of "lifetime until end of full expression" already cover the common use cases of calling functions with temporary arguments.
The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
In the by-reference case, we get a const Base& reference that refers to a Derived object. The entire temporary object, of type Derived , is lifetime-extended.
The simple answer is that you need to be able to bind a temporary with a const reference, not having that feature would require a good amount of code duplication, with functions taking const&
for lvalue or value arguments or by-value for rvalue arguments.
Once you need that the language needs to define some semantics that will guarantee the lifetime of the temporary is at least as long as that of the reference.
Once you accept that a reference can bind to an rvalue in one context, just for consistency you may want to extend the rule to allow the same binding in other contexts, and the semantics are really the same. The temporary lifetime is extended until the reference goes away (be it a function parameter, or a local variable).
The alternative would be rules that allow binding in some contexts (function call) but not all (local reference) or rules that allow both and always create a dangling reference in the latter case.
Removed the quote from the answer, left here so that comments would still make sense:
If you look at the wording in the standard there are some hints as of this intended usage:
12.2/5 [middle of the paragraph] [...] A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call. [...]
As Bjarne Stroustrup (the original designer) explained it in a clc++ posting in 2005, it was for uniform rules.
The rules for references are simply the most general and uniform I could find. In the cases of arguments and local references, the temporary lives as long as the reference to which it is bound. One obvious use is as a shorthand for a complicated expression in a deeply nested loop. For example:
for (int i = 0; i<xmax; ++i) for (int j = 0; j< ymax; ++j) { double& r = a[i][j]; for (int k = 0; k < zmax; ++k) { // do something with a[i][j] and a[i][j][k] } }
This can improve readability as well as run-time performance.
And it turned out to be useful for storing an object of a class derived from the reference type, e.g. as in the original Scopeguard implementation.
In a clc++ posting in 2008, James Kanze supplied some more details:
The standard says exactly when the destructor must be called. Before the standard, however, the ARM (and earlier language specifications) were considerably looser: the destructor could be called anytime after the temporary was "used" and before the next closing brace.
(The “ARM” is the Annotated Reference Manual by (IIRC) Bjarne Stroustrup and Margareth Ellis, which served as a de-facto standard in the last decade before the first ISO standard. Unfortunately my copy is buried in a box, under a lot of other boxes, in the outhouse. So I can't verify, but I believe this is correct.)
Thus, as with much else the details of lifetime extensions were honed and perfected in the standardization process.
Since James has raised this point in comments to this answer: that perfection could not reach back in time to affect Bjarne's rationale for the lifetime extension.
Example of Scopeguard-like code, where the temporary bound to the reference is the full object of derived type, with its derived type destructor executed at the end:
struct Base {};
template< class T >
struct Derived: Base {};
template< class T >
auto foo( T ) -> Derived<T> { return Derived<T>(); }
int main()
{
Base const& guard = foo( 42 );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With