I'll try!
The problem is fundamentally the lifetime of the referent.
Consider the incorrect function
int& f() { int x = 42; return x; }
Which always returns a
dangling lvalue reference to x. No matter the context of the call,
x is destroyed at the closing brace of
f. Maybe this isn't apparent, but this is true even in a context like
int const& foo = f()
Where we fail to extend the lifetime of x, because the result of the function is not x, but a reference to it. Merely initializing another reference (
foo
) from the result of the function doesn't affect the lifetime of x. Indeed, there is nothing you can do to prevent
x from being destroyed at the closing brace.
Now consider the original:
const string& r = join(temp(), "orary");
join()
doesn't exactly return a dangling reference: it returns a reference to the result of the expression
temp()
. Then we initialize
r from the return value.
We saw before (i.e., in [class.temporary]) that the temporary returned by
temp()
lives only until the semicolon. This is bad, because
r
clearly can be used after the semicolon.
To fix it, we need to force r to bind a result that isn't about to die. We do that by creating a new object - the result of
join()
, and move-initializing it with temp:
1 2 3 4 5
|
string join(string&& rv, const char* s) {
// initialize the result of the function (a std::string) from
// the result of the expression move(rv.append(s));
return move(rv.append(s));
}
|
vs.
1 2 3 4 5 6
|
string&& join(string&& rv, const char* s) {
// return a reference to the result of the expression
// move(rv.append(s));
// we don't know how long the referent bound to rv will live
return move(rv.append(s));
}
|