10

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.

Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • with `auto&& val = wrapper{O()}.val`, no. – Jarod42 Nov 08 '13 at 17:03
  • @Jamboree Do you ask specifically about `auto&&` or just lifetime extension? (After `auto&&` has been deduced, the default rules for reference binding apply. It's either a `wrapper const&` or `wrapper&&` or the binding will fail.) – dyp Nov 08 '13 at 17:57
  • @Jamboree IMO N3690 is currently a weird document to refer to. It includes some C++1y features, but [N3979](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3797.pdf) is the latest publicly available draft (besides the [github repository](https://github.com/cplusplus/draft)). – dyp Nov 08 '13 at 18:01
  • See http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#1697 . EDG and GCC will not extend the lifetime. My understanding of the intent has been so far that it won't be extended (i considered the behavior of the implementations "correct" rather than the description in the Standard). We need to see how that issue will be resolved and whether the implementations officially will be kept wrong. – Johannes Schaub - litb Nov 08 '13 at 19:39
  • In particular, see the last bullet example in 12.2p5, which also initializes a reference member and where you can see that the lifetime is extended, according to the example's comment – Johannes Schaub - litb Nov 08 '13 at 20:06
  • @Dyp: what I mean is that `~O()` (I miss this one in my first reading) and `~wrapper()` are called, so the member of the temporary is really unsafe to use. – Jarod42 Nov 09 '13 at 11:02
  • @Jarod42 Ah! I overlooked that `wrapper` stores a reference. I'm not sure if `~wrapper` will be called before the scope of `val` ends: Typically, the lifetime of a temporary object will be extended by binding a reference to it or to a subobject of that temporary. However, references are not objects, so I'm not sure if this applies here. – dyp Nov 09 '13 at 11:13
  • @DyP: thanks, I don't know lifetime expands with ref to subobject (I tested and references are not object), but it doesn't work with getter() through. – Jarod42 Nov 09 '13 at 16:06

1 Answers1

7

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

Community
  • 1
  • 1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • If you look at the live demo, there is some strange thing.. the `O` in the second `wrapper` has a short lifespan – SwiftMango Nov 08 '13 at 18:59
  • @texasbruce I've updated the post to give further explanations. I hope this helps to clarify the matter. – Cassio Neri Nov 08 '13 at 19:40
  • Just googled dangling reference to rvalue... interesting that the class is copied but the reference member is destroyed and dangling.. – SwiftMango Nov 08 '13 at 21:51
  • Remark: So the reference is not transitive, see my additional cases (3 & Ex) in which the lifetime is not extended. Case 1 with default initializer is really a special case. – Jamboree Nov 09 '13 at 06:14