Is capturing a newly constructed object by const ref undefined behavior
There should be no issue here, thanks to lifetime extension. The newly constructed object will survive until the reference goes out of scope.
Yes this is perfectly safe: the binding to a const
reference extends the lifetime of the temporary to the scope of that reference.
Note that the behaviour is not transitive though. For example, with
const auto& cc = []{
const auto& c = SomeClass{};
return c;
}();
cc
dangles.
It is safe. Const ref prolongs the lifetime of temporary. The scope will be the scope of const ref.
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.
Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:
- a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
- a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696).
- a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.
- a temporary bound to a reference in the initializer used in a new-expression exists until the end of the full expression containing that new-expression, not as long as the initialized object. If the initialized object outlives the full expression, its reference member becomes a dangling reference.
- a temporary bound to a reference in a reference element of an aggregate initialized using direct-initialization syntax (parentheses) as opposed to list-initialization syntax (braces) exists until the end of the full expression containing the initializer.
struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference
In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.
as @Konrad Rudolph pointed out (and see the last paragraph of above):
"If
c.GetSomeVariable()
returns a reference to a local object or a reference that it is itself extending some object’s lifetime, lifetime extension does not kick in"
This is safe.
[class.temporary]/5
: There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. [..]
[class.temporary]/6
: 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 the glvalue to which the reference is bound was obtained through one of the following: [lots of things here]