Temporary lifetime extension is achieved when a reference variable is bonded to a temporary directly, but not only. For the exact list of temporary lifetime extension, see in the specification: [class.temporary].
The answer provided by his holiness @HolyBlackCat is very good, but I feel some examples are required.
⦿ binding a temporary directly
// function prototype
std::string foo();
// calling foo:
const auto& b = foo(); // lifetime is extended, directly bind to a temporary
// also, similarly:
const std::string& s = "hi"; // lifetime is extended, the same
According to the language rules, temporary lifetime extension can be also achieved in any of the following cases:
⦿ parentheses ( ) (grouping, not a function call)
const auto& a = (foo()); // lifetime is extended, grouping with parenths is ok
const std::string& s = ("hello "s + "world"); // lifetime is extended, the same
For the next cases, let's add the following struct:
struct A {
std::string str = "hey";
int arr[3] = {2, 3, 4};
int* ptr = arr;
const auto& foo() const {
return str;
}
};
⦿ member access ., .*
const auto& b1 = A().str; // lifetime of A() is extended
const auto& b2 = A().arr; // lifetime of A() is extended
const auto& b3 = A().ptr; // lifetime of A() is extended
// BUT -
const auto& b4 = *A().ptr; // lifetime of A() is NOT extended (b4 dangling)
// pointer to member access
const auto& str_ptr = &A::str;
const auto& arr_ptr = &A::arr;
const auto& ptr_ptr = &A::ptr;
const auto& c1 = A().*str_ptr; // lifetime of A() is extended
const auto& c2 = A().*arr_ptr; // lifetime of A() is extended
const auto& c3 = A().*ptr_ptr; // lifetime of A() is extended
// BUT - not for a member function
const auto& foo_ptr = &A::foo;
// below initialization is bounded to a function call result
// not to a member access
const auto& c4 = (A().*foo_ptr)(); // lifetime of A() is NOT extended (c4 dangling)
⦿ array access [ ] (not overloaded; must use an array and not a pointer)
const auto& d1 = A().arr[0]; // lifetime of A() is extended
// BUT - not for pointers
// pointer access with []
const auto& d2 = A().ptr[0]; // lifetime of A() is NOT extended (d2 dangling)
// neither for overloaded []
const auto& d3 = A().str[0]; // lifetime of A() is NOT extended (d3 dangling)
⦿ ternary operator ? :
const auto& e1 = true? A() : A(); // lifetime of the 1st A() is extended
const auto& e2 = false? A() : A(); // lifetime of the 2nd A() is extended
⦿ comma operator , (not overloaded)
const auto& f1 = (A(), A()); // lifetime of the 2nd A() is extended
⦿ any cast that doesn't involve a "user-defined conversion" (presumably uses no constructors nor conversion operators)
const auto& g1 = const_cast<const A&&>(A()); // lifetime of A() is extended
const double& g2 = A().arr[0]; // lifetime of A() is NOT extended
// but this is a valid ref to a double
// converted from an int, as a temporary
For a casting that doesn't extend lifetime, let's add an additional class:
class B {
const A& a;
public:
B(const A& a): a(a){}
};
The following casting goes through user-defined casting and thus will not extend the life time of A:
const auto& g3 = ((B&&)A()); // lifetime of A() is NOT extended (g3 dangling)