Extension of the lifetime of temporaries by binding to a reference: general rule and three exceptions
This is a quick note on the rules concerning the extension the lifetime of temporaries when bound to a reference. The main rule is introduced, the three exceptions presented and an example is given.
The « standard » means the draft n4659 unless mentioned otherwise. This draft should be very close to the final c++17 standard. (See this question on stackoverflow)
The rules
The rules are given in 15.2 [class.temporary]/6.
In general when a temporary is bound to a reference its lifetime is extended to the lifetime of the reference, with three exceptions:
- ref. param — When a temporary is bound to a reference parameter of a function call, the lifetime of the temporary is extended until the end of the full expression.
- return value — The lifetime of a temporary bound to the return value of a function is not extended.
- new expression — When a temporary is bound to a reference in a new expression, the lifetime of the temporary is extended until the end of the full expression (and not until the delete).
Example
Given:
#include <iostream>
#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << std::endl)
struct X{
X() { PRETTY(this); }
~X() { PRETTY(this); }
};
struct Y {
int i;
const X& rx;
};
Example: general case
In general the lifetime of temporaries is extended:
const X& rx = X{};
std::cout << "Do stuff with rx... " << &rx << '\n';
will print:
X::X() : 0x7ffd8fa6a338
Do stuff with rx... 0x7ffd8fa6a338
X::~X() : 0x7ffd8fa6a338
Example: exception « ref. param »
auto lambda = [](const X& rx) -> void { std::cout << "Do stuff with rx... " << &rx << '\n'; };
(lambda(X{}), std::cout << "Do stuff after calling lambda" << '\n');
will print:
X::X() : 0x7ffd8fa6a320
Do stuff with rx... 0x7ffd8fa6a320
Do stuff after calling lambda
X::~X() : 0x7ffd8fa6a320
Example: exception « return value »
auto lambda = []() -> X& { X x{}; std::cout << "Do stuff inside the lambda with x... " << &x << '\n'; return x;};
X& rx = lambda();
std::cout << "Do stuff after calling lambda with rx, oops rx is dangling... " << &rx << '\n';
will print:
X::X() : 0x7ffd8fa69d88
Do stuff inside the lambda with x... 0x7ffd8fa69d88
X::~X() : 0x7ffd8fa69d88
Do stuff after calling lambda with rx, oops rx is dangling... 0x7ffd8fa69d88
Example: exception « new expression »
Y* py = nullptr;
(py = new Y{42, X{}}, std::cout << "Do stuff with py->rx..." << &(py->rx) << '\n');
std::cout << "Do more stuff with py->rx, oops dangling reference... " << &(py->rx) << '\n';
delete py;
will print:
X::X() : 0x7ffd8fa6a300
Do stuff with py->rx...0x7ffd8fa6a300
X::~X() : 0x7ffd8fa6a300
Do more stuff with py->rx, oops dangling reference... 0x7ffd8fa6a300
Full code is available here.