Reputation: 343
In Range-based for loop on a temporary range, Barry mentioned that the following is not affected by the destroyed temporary object, and I tested member v
indeed exists throughout the for
-loop (as the destructor ~X
didn't get called throughout the for
-loop). What is the explanation?
struct X {
std::vector<int> v;
~X()
{
}
};
X foo()
{
return X();
}
for (auto e : foo().v) {
// ok!
}
Upvotes: 14
Views: 1954
Reputation: 13790
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)
Upvotes: 1
Reputation: 96579
This is an obscure form of temporary lifetime extension. Normally you have to bind the temporary directly to the reference for it to work (e.g. for (auto x : foo())
), but according to cppreference, this effect propagates through:
( )
(grouping, not a function call),[ ]
(not overloaded; must use an array and not a pointer),.
, .*
,? :
,,
(not overloaded),I.e. if a.b
is bound to a reference, the lifetime of a
is extended.
Upvotes: 17