2

I'm not arguing against the result of the code below, for I think it's correct to assume that a const lvalue reference and an rvalue reference, both extend the lifetime of the temporary returned from the function. What surprises me is this paragraph in the Standard, which appears to say the contrary:

12.2p5 (emphasis mine):

The second context is when a reference is bound to a 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:

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

Example code:

#include <iostream>

struct A{ A() : i(2) {} int i;};

A f() { A a; return a; }

int main()
{
    A&& a1 = f();
    std::cout << a1.i << '\n';
    const A& a2 = f();
    std::cout << a2.i << '\n';
}
Community
  • 1
  • 1
Belloc
  • 6,318
  • 3
  • 22
  • 52
  • 3
    I'm not clear what your question is. What output are you getting from your program? What is unexpected about it? Which version of which compiler are you testing with? – Jonathan Leffler Jun 27 '13 at 21:36
  • 9
    `A const& f(){ return A(); }` – Xeo Jun 27 '13 at 21:37
  • 2
    I don't think the bullet point you quote applies to your example. I believe it's referring to something like `return ;`, not assigning the return value of a function to a reference. – David Brown Jun 27 '13 at 21:37
  • @JonathanLeffler The program is printing the values for a1.i and a2.i correctly showing that the lifetime of the temporary returned by the function is extended. – Belloc Jun 27 '13 at 21:38
  • 2
    @user1042389: Relying on such output is a bad idea, as the memory might have simply survived the nuke and still contain what it contained before. A better test is outputting something directly in the constructors and destructor. – Xeo Jun 27 '13 at 21:40
  • @DavidBrown The result is the same if function `f()` was defined as `A f() { return A(); }` – Belloc Jun 27 '13 at 21:41
  • @Xeo If I include another `std::cout << a1.i << '\n';` at the end of main() it still prints the correct result. – Belloc Jun 27 '13 at 21:44
  • 1
    @user1042389 in that case the returned type is not a reference, so the temporary is assigned to the returned value and then destroyed, no lifetime extension needed. See Xeo's comment for an example where this exception applies. – David Brown Jun 27 '13 at 21:44
  • Can't say anything better than @Xeo's comment. That should be the answer. – Kerrek SB Jun 27 '13 at 21:46
  • I don't know the C++ rules in this area, but generally if an object's lifetime ends, that doesn't guarantee that an attempt to access it will fail. It may be that the chunk of memory that held the object happens not to have been reused for something else. – Keith Thompson Jun 27 '13 at 21:48
  • @DavidBrown Then where in the Standard is the clause that says the references in the snippet extend the lifetime of the temporary returned by the function f()? – Belloc Jun 27 '13 at 21:50
  • 1
    @KeithThompson There's no doubt that the lifetime of temporaries are extended. If you define a destructor for class A with any message, it will be printed at the end of main(). – Belloc Jun 27 '13 at 21:53
  • 1
    The first part of your standard quote says that the temporary created by the expression `f()` will have its lifetime extended because it's assigned to a reference. The exception you quote only applies to functions that return references. – David Brown Jun 27 '13 at 22:36

2 Answers2

5

The quote you mention is specifically for returning a reference from a function and binding that reference to a temporary:

const T& f() { return T(); };

That is not the case in your code because you are binding a temporary to a reference at the call side, not in the return statement. In your comments you mention that when you modify the code to:

T f() { return T(); }
T&& r = f();

the lifetime is still extended, but that is wrong. The temporary lives for the duration of the return statement during which it gets copied to the returned value. After the copy completes the lifetime of the temporary ends. On the calling side you have a different temporary (the result of f()) whose lifetime gets extended.

But there's no doubt that the lifetime of temporaries are extended. If you define a destructor for class A with any message, it will be printed at the end of main(), or is there?

That statement is also incorrect. You are seeing the effects of the return value optimization (RVO). Instead of creating a temporary for the T() inside the function and another for the returned value, the compiler is creating the two objects in the same location. You are probably seeing a single object in the output of the program, but in theory there are two.

You can try using g++ with -fno-elide-constructors and you should be able to see both temporaries, one of which is extended the other will not be.

Alternatively, you can return a reference:

const A& f() { return A(); }
const A& r = f();

Which should show how the temporary dies before r goes out of scope.


This is basically the same test inhibiting the

$ g++ --version | head -1

g++ (GCC) 4.3.2

$ cat x.cpp

#include <iostream>

struct X {
    X() { std::cout << "X\n"; }
    ~X() { std::cout << "~X\n"; }
};

X f() { return X(); }

int main() {
    const X& x = f();
    std::cout << "still in main()\n";
}

$ g++ -o t1 x.cpp && ./t1

X
still in main()
~X

$ g++ -fno-elide-constructors -o t2 x.cpp && ./t2

X
~X
still in main()
~X

$ clang++ -version | head -1

$ clang version 3.2 (tags/RELEASE_32/final)

$ clang++ -fno-elide-constructors -o t3 x.cpp && ./t3

X
~X
still in main()
~X

$ cat y.cpp

#include <iostream>

struct X {
    X() { std::cout << "X\n"; }
    ~X() { std::cout << "~X\n"; }
};

const X& f() { return X(); }

int main() {
    const X& x = f();
    std::cout << "still in main()\n";
}

$ g++ -fno-elide-constructors -o t4 y.cpp && ./t4

X
~X
still in main()
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • The temporary dies even before `r` is initialized. Whatever you get out of `f()` is already a dangling reference. Also, are you sure about `T& f()`? Missing a `const` or `&` there? – Xeo Jun 27 '13 at 22:07
  • There's no chance for an RVO in the snippet as the function returns a named object. – Belloc Jun 27 '13 at 22:08
  • 2
    @user1042389 *Named* Return Value Optimization (NRVO) would like a word with you. – Xeo Jun 27 '13 at 22:09
  • 1
    @user1042389: From your comment: *The result is the same if function f() was defined as `A f() { return A(); }`*, there RVO will kick in. Even in `A f() { A a; return a; }` the compiler will perform *Named RVO* which is itself a form of RVO – David Rodríguez - dribeas Jun 27 '13 at 22:12
  • I must say that NRVO is not the case also, as the results above were obtained in a debug build in VS2010. MS compilers don't apply NRVO in debug builds. – Belloc Jun 27 '13 at 22:12
  • @user1042389: I don't know what VS does or when it applies the different things that are called (N)RVO. I can tell you that in gcc you can force the creation of all objects with `-fno-elide-constructors` and that for `T f() { return T(); } const T& r = f();` there are two traces of the destructor being run, one for the `T()` inside the function, one for the temporary whose lifetime is extended by `r`. – David Rodríguez - dribeas Jun 27 '13 at 22:16
  • @user1042389: Test run of a similar program with g++ and clang++ inhibiting RVO and NRVO through `-fno-elide-constructors`. It should be obvious from the output of the program that the lifetime is not extended, if it is not we can discuss – David Rodríguez - dribeas Jun 27 '13 at 22:25
4

The second context is when a reference is bound to a temporary - except - The lifetime of a temporary bound to the returned value in a function return statement is not extended

A f() { A a; return a; }

First of all, a isn't a temporary. This may be what you were thinking of:

A f() { return A(); }

Secondly, the return type of the function is not a reference type. Here is when the rule would apply:

const A& f() { return A(); }

The temporary from A() is being bound to the return type of const A&. As per the rule, the lifetime of the temporary is not extended.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • When I read the third bullet point in 12.2p5 I don't see how does it relate to the case `A const& f(){ return A(); }`, although I believe that's the correct answer. Could you explain this? – Belloc Jun 27 '13 at 22:37
  • @user1042389: So the statement `return EXPR` causes the return value of the function to be initialized with the expression `EXPR`. The return value has the return type of the function. I am not sure what specifically you are not understanding. – Andrew Tomazos Jun 27 '13 at 22:43