class C
{
public:
int True(int i) const
{
return i+2;
}
};
const C& F(const C& c)
{
return c;
}
int main()
{
const C& c =
Normally, when a temporary is bound to a const reference, the lifetime of the temporary is extended to the lifetime of the reference. Thus if your code said const C& c = C()
then the temporary would live as long as c
does.
However, you're passing the temporary to another function F()
. In this case, §12.2.5 of the C++11 spec dictates that the temporary will persist until the completion of the full-expression containing the call.
Therefore, when you say const C& c = F(C())
, the temporary C()
is actually destructed at the end of this statement, and is no longer valid on the next line.
That said, your code appears to function properly because the call to c.True()
is known at compile-time, and the function definition doesn't actually refer to any data in c
, so the fact that the temporary is dead doesn't really affect the observed behavior. However this is technically undefined behavior.
Paragraph 12.2/4 of the C++11 Standard specifies that in some situations the lifetime of temporaries can indeed be extended beyond the end of the full expression in which they are generated:
There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. [...]
The first context is not relevant. However, per Paragraph 12.2/5:
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:
— A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
— A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
— 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.
— A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.
Here, the temporary constructed with C()
is bound to the argument c
of function F
. Therefore, the temporary is destroyed at the end of the full-expression which contains the call to function F()
, and the returned reference is dangling.
Invoking function True()
on it causes Undefined Behavior.
This invokes undefined behaviour. As soon as the full expression involving F(C())
completes, the temporary created by C()
is destroyed, so the reference returned by F
is no longer valid.
However, undefined behaviour does not guarantee your program will crash. In more annoying cases (like this) it simply causes subtle, hard to diagnose bugs. As for why this undefined behaviour gives you this specific result, I refer you to this famous answer.
What you are seeing is undefined behaviour. It is fairly likely that, since the function you call does not depend on the object's state or vtable at all, the compiler inlined it to cout << boolalpha << ( 1+2 );
, so it does not matter whether or not the object has been destroyed - in fact, the compiler may not have even bothered to create it in the first place.
For example, with VS2010, in 'Debug' it calls F
and True
as static calls. As True
does not reference this
the code in it happens to work fine. ( it may even still work even if it did, as there are no member variables in C
to access, so the only thing it could do would be to print out the address of this
, and that would just be an address on the stack. If C
had member variables which were altered by C
's destructor and True
used them, then you would see a difference - in all cases, the behaviour is undefined and just an artefact of the implementation )
In 'Release' VS2010 does not bother to create any C
object or call F
or True
- it simply calls cout << boolalpha << 3
, having determined the value of C::True(2)
at compile time. There is no temporary C
object, invalid or not, in the program the compiler generates.
So just because calling a function on an object appears to work, it does not imply that the object exists, or ever existed, in the program generated by the compiler. A different source program with different undefined behaviour may cause the compiler to generate a executable which raises an access violation, or exhibit some other behaviour.
The binding a return value to const reference only applies to return by value, not returning a reference to a parameter or local, otherwise the compiler would need to solve the halting problem to determine the lifecycle of objects.
For example, this code:
#include<iostream>
class C
{
public:
int t;
C( int t ) : t(t){}
~C() { std::cout << __FUNCTION__ << " " << t << std::endl; }
};
const C& F(const C& c)
{
return c;
}
const C& G()
{
return C(2);
}
C H()
{
return C(3);
}
int main()
{
const C& c = F(C(1));
std::cout << "main 1" << std::endl;
const C& d = G();
std::cout << "main 2" << std::endl;
const C& e = H();
std::cout << "main 3" << std::endl;
}
results in this output - only H()
returns by value, so only the 3rd C has its lifecycle extended:
C::~C 1
main 1
C::~C 2
main 2
main 3
C::~C 3