The code below illustrated my concern:
#include <iostream>
struct O
{
~O()
{
std::cout << "~O()\n";
}
};
struct wrapper
{
O const& val;
~wrapper()
{
std::cout << "~wrapper()\n";
}
};
struct wrapperEx // with explicit ctor
{
O const& val;
explicit wrapperEx(O const& val)
: val(val)
{}
~wrapperEx()
{
std::cout << "~wrapperEx()\n";
}
};
template<class T>
T&& f(T&& t)
{
return std::forward<T>(t);
}
int main()
{
std::cout << "case 1-----------\n";
{
auto&& a = wrapper{O()};
std::cout << "end-scope\n";
}
std::cout << "case 2-----------\n";
{
auto a = wrapper{O()};
std::cout << "end-scope\n";
}
std::cout << "case 3-----------\n";
{
auto&& a = wrapper{f(O())};
std::cout << "end-scope\n";
}
std::cout << "case Ex-----------\n";
{
auto&& a = wrapperEx{O()};
std::cout << "end-scope\n";
}
return 0;
}
See it live here.
It's said that auto&&
will extend the life-time of the temporary object, but I can't find the standard words on this rule, at least not in N3690.
The most relevant may be section 12.2.5 about temporary object, but not exactly what I'm looking for.
So, would auto&& life-time extension rule apply to all the temporary objects involved in the expression, or only the final result?
More specific, is a.val
guaranteed to be valid (non-dangling) before we reach the end-of-scope in case 1?
Edit: I updated the example to show more cases (3 & Ex).
You'll see that only in case 1 the lifetime of O is extended.
In the same way that a reference to
const
does:or
or also
Yes, it is.
There's (almost) nothing particularly important about
auto
here. It's just a place holder for the correct type (wrapper
) which is deduced by the compiler. The main point is the fact that the temporary is bound to a reference.For more details see A Candidate For the “Most Important const” which I quote:
The article is about C++ 03 but the argument is still valid: a temporary can be bound to a reference to
const
(but not to a reference to non-const
). In C++ 11, a temporary can also be bound to an rvalue reference. In both cases, the lifetime of the temporary is extended to the lifetime of the reference.The relevant parts of the C++11 Standard are exactly those referred in the OP, that is, 12.2 p4 and p5:
(There are some exceptions in the bullet points following these lines.)
Update: (Following texasbruce's comment.)
The reason why the
O
in case 2 has a short lifespan is that we haveauto a = wrapper{O()};
(see, there's no&
here) and then the temporary is not bound to a reference. The temporary is, actually, copied intoa
using the compiler generated copy-constructor. Therefore, the temporary doesn't have its lifetime expanded and dies at the end of the full expression in which it appears.There's a danger in this particular example because
wrapper::val
is a reference. The compiler generated copy-constructor ofwrapper
will binda.val
to the same object that the temporary'sval
member is bound to. This object is also a temporary but of typeO
. Then, when this latter temporary dies we see~O()
on the screen anda.val
dangles!Contrast case 2 with this:
The output is (when compiled with gcc using option
-fno-elide-constructors
)Now the temporary
wrapper
has itsval
member bound too
. Notice thato
is not a temporary. As I said,a
is a copy of thewrapper
temporary anda.val
also binds too
. Before the scope ends the temporarywrapper
dies and we see the first~wrapper()
on the screen.Then the scope ends and we get
end-scope
. Now,a
ando
must be destroyed in the reverse order of construction, hence we see~wrapper()
whena
dies and finally~O()
when it'so
's time. This shows thata.val
doesn't dangle.(Final remark: I've used
-fno-elide-constructors
to prevent a optimization related to copy-construction that would complicate the discussion here but this is another story.)