The code below illustrated my concern:
#include
struct O
{
~O()
{
std::cout << \"~O()\\n\";
}
};
struct wrapper
In the same way that a reference to const
does:
const auto& a = wrapper{O()};
or
const wrapper& a = wrapper{O()};
or also
wrapper&& a = wrapper{O()};
More specific, is
a.val
guaranteed to be valid (non-dangling) before we reach the end-of-scope in case 1?
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:
Normally, 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 on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself
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:
4 - There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. The first context is [...]
5 - The second context is when a reference is bound to a temporary. [...]
(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 have auto a = wrapper{O()};
(see, there's no &
here) and then the temporary is not bound to a reference. The temporary is, actually, copied into a
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 of wrapper
will bind a.val
to the same object that the temporary's val
member is bound to. This object is also a temporary but of type O
. Then, when this latter temporary dies we see ~O()
on the screen and a.val
dangles!
Contrast case 2 with this:
std::cout << "case 3-----------\n";
{
O o;
auto a = wrapper{o};
std::cout << "end-scope\n";
}
The output is (when compiled with gcc using option -fno-elide-constructors
)
case 3-----------
~wrapper()
end-scope
~wrapper()
~O()
Now the temporary wrapper
has its val
member bound to o
. Notice that o
is not a temporary. As I said, a
is a copy of the wrapper
temporary and a.val
also binds to
o
. Before the scope ends the temporary wrapper
dies and we see the first ~wrapper()
on the screen.
Then the scope ends and we get end-scope
. Now, a
and o
must be destroyed in the reverse order of construction, hence we see ~wrapper()
when a
dies and finally ~O()
when it's o
's time. This shows that a.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.)