9

There are some unclear information for me about extension of lifetime of an object returned from function and bound to rvalue/const lvalue reference. Information from here.

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.

If I understand it correctly, the quote claims that the lifetime of objects returned by return statements is not extendable. But the last sentence suggests, this only applies to functions returning references.

On GCC, this code produces the output below:

struct Test
{
    Test() { std::cout << "creation\n"; }
    ~Test() { std::cout << "destruction\n"; }
};
    
Test f()
{
    return Test{};   
}
    
int main()
{
    std::cout << "before f call\n";
    Test && t = f();
    std::cout << "after f call\n";
}

before f call
creation
after f call
destruction

So it looks like the lifetime got extended.
Should the lifetime of a temporary object bound to such reference be extended? Also could you provide any more clear source of informations?

Marc
  • 338
  • 2
  • 15
Criss
  • 581
  • 5
  • 17
  • Yes it should, but I don't know were it is specified in the standard. – Guillaume Racicot Apr 11 '17 at 15:12
  • `Test && t = f();` - that code makes no sense. Why do you declare object as rvalue? – Mariusz Jaskółka Apr 11 '17 at 15:13
  • @Criss sorry, removed comment, because I overlooked that `&&` which may cause stuff that I am not familiar with. However, I dont really understand the question, as your `f` doesnt return a reference, but a copy of the local temporary, so everything should be fine – 463035818_is_not_an_ai Apr 11 '17 at 15:15
  • 1
    @jaskmar it's not an rvalue, it's an rvalue reference - those are 2 different things. rvalue is a value category, rvalue reference is a type – Steve Lorimer Apr 11 '17 at 15:16
  • hm... maybe the lifetime of the returned temporary is supposed to end at the end of the line, but then still I dont see the relevance of the quote, as it is about returning a reference from a function. My guess would be that RVO is the reason you dont see the `destruction` of the temporary inside `f` – 463035818_is_not_an_ai Apr 11 '17 at 15:18
  • @tobi303 Its ok :) That's what I'm not sure about. I don't know if above quote applies only to functions returning references or to return statement at all. – Criss Apr 11 '17 at 15:19
  • @SteveLorimer you are right but still- the code makes no sense. It is "universal reference" in this context and works like if there were no `&&` at all. – Mariusz Jaskółka Apr 11 '17 at 15:20
  • @jaskmar The code makes sense just fine. And it's not a universal reference, that would be `auto &&`. – Nir Friedman Apr 11 '17 at 15:21
  • @tobi303 How you know it is about returning reference? – Criss Apr 11 '17 at 15:22
  • @Criss because there is no such thing as a "dangling" value. The problem only arises when you return refrences or pointers. Returning by value is completely safe always, as you get a copy, while simply copying a pointer in general does not keep the pointed-to object alive – 463035818_is_not_an_ai Apr 11 '17 at 15:27
  • @Criss btw dont trust me on anything, I also have too little knowledge on that to give an answer – 463035818_is_not_an_ai Apr 11 '17 at 15:29

2 Answers2

9

So it looks like the lifetime got extended.

The code is pretty valid, but note that the object whose lifetime got extended is not the temporary object created inside the function f() by Test{}, it's the returned object by the function f(). That returned object is move-constructed from the temporary object, then gets bound to t and lifetime gets extended. BTW the returned object is returned by value, and it's a temporary too.

For observation you can add move constructor manually:

struct Test
{
  Test() { std::cout << "creation\n"; }
  ~Test() { std::cout << "destruction\n"; }
  Test(Test&&) { std::cout << "move\n"; }
};

and compile and run with forbidding copy elision mode, the result is:

before f call
creation      // the temporary created inside f
move          // return object move-constructed
destruction   // the temporary destroyed
after f call
destruction   // the returned object destroyed

LIVE


Quotes from the standard, §15.2/6 Temporary objects [class.temporary]:

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

(6.1) A temporary object bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.

(6.2) The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

(6.3) A temporary bound to a reference in a new-initializer persists until the completion of the full-expression containing the new-initializer. [ Example:

struct S { int mi; const std::pair<int,int>& mp; };
S a { 1, {2,3} };
S* p = new S{ 1, {2,3} };   // Creates dangling reference

 — end example ] [ Note: This may introduce a dangling reference, and implementations are encouraged to issue a warning in such a case.  — end note ]

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Ok, so lifetime of temporary (not exactly that created in f, I know) is getting extended, yes? Can you also point where standard says about these things? – Criss Apr 11 '17 at 16:43
  • @Criss Yes. And what the standard says is the same as cppreference.com; anyway I added to the answer. – songyuanyao Apr 11 '17 at 16:51
  • 3
    Good answer - but please note that since C++17 copy elision is mandatory so no copy nor move constructor can be invoked. But the lifetime still will be extended. – Mariusz Jaskółka Apr 12 '17 at 12:14
  • Why after destruction of temporary object moved object not destructed also? – IC_ Apr 25 '18 at 04:31
  • @Herrgott Which object are you referring to? – songyuanyao Apr 25 '18 at 05:15
  • @songyuanyao object that is left to function call `myNewObj = getNewObject()` – IC_ Apr 25 '18 at 10:33
  • @Herrgott I still can't get your questioin correctly.. For `Test && t = f();`, `t` is a reference, binding to the returned object from `f()`. The returned object is a temporary object, whose lifetime is extended to the lifetime of `t`. So when `t` get out of the scope the temporary object being bound will be destroyed. – songyuanyao Apr 25 '18 at 10:37
1

Quoting from a GOTW article

A temporary object lasts only until the end of the full expression in which it appears. However, C++ deliberately specifies that binding a temporary object to a reference to const (or ravlue reference) on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself, and thus avoids what would otherwise be a common dangling-reference error.

string f() { return "abc"; }

void g() {
const string& s = f();
  cout << s << endl;    // can we still use the "temporary" object?
}

In the example above, the temporary returned by f() lives until the closing curly brace. (Note this only applies to stack-based references. It doesn’t work for references that are members of objects.)

For legalese, read this SO answer

The answer applies to both local const references and rvalue references

Community
  • 1
  • 1
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • imho the presence of the function is a bit confusing (also in the question). Wouldnt it be exactly same situation and reasoning if it was `const string& s = "abc";` ? – 463035818_is_not_an_ai Apr 11 '17 at 15:25