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.valguaranteed 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.)