7

For the code below:

#include <iostream>
#include <memory>
#include <string>
using namespace std;

struct Foo {
    string tag;

    Foo(string t): tag(t){
        cout << "Foo:" << tag << endl;
    }
    ~Foo() {
        cout << "~Foo:" << tag << endl;
    }
};

struct Bar {
    Foo&& foo;
};

struct Baz{
    Foo&& foo;
    Baz(Foo&& f):foo(std::move(f)){

    }
};

int main() {
    Bar bar{Foo("Bar")};
    Baz baz{Foo("Baz")};
    cin.get();
}

result(g++ 7.1.0):

Foo:Bar
Foo:Baz
~Foo:Baz

We can see that bar successfully extend the lifetime of a temporary Foo but baz failed to do so. What is the difference between the two? How can I implement the constructor of Baz correctly?

Edit: actually VC++2017 gives:

Foo:Bar
~Foo:Bar
Foo:Baz
~Foo:Baz

So I guess the whole thing is not reliable.

W.H
  • 1,838
  • 10
  • 24
  • Binding a temporary to reference in the constructor initializer list only extends the lifetime through the end of that constructor. This is [documented behavior](http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary). Practically speaking, there's no place in memory for the compiler to stash the temporary past the constructor's return. – Igor Tandetnik Jul 25 '17 at 04:24
  • Failure to output `~Foo:Bar` at any stage would be a gross compiler bug – M.M Jul 25 '17 at 04:26
  • With g++ 7.1.0 for your exact code, I get `~Foo:Bar` as the fourth line of output, can you recheck? – M.M Jul 25 '17 at 04:31
  • @M.M https://wandbox.org/permlink/cUqlBvR6BexveDBX `~Foo:Bar` is not invoked immediately (like `~Foo:Baz`). – songyuanyao Jul 25 '17 at 04:34
  • @songyuanyao The question appears to show it never being invoked at all – M.M Jul 25 '17 at 04:35
  • 1
    @M.M Yes. But I think OP wants to know the different between the 2 cases, i.e. why `~Foo:Bar` is not invoked immediately when the whole expression ends (where the temporary is supposed to be destroyed). – songyuanyao Jul 25 '17 at 04:37
  • @songyuanyao If it is not invoked at all, the answer is clearly "bugged compiler" – M.M Jul 25 '17 at 04:40
  • @M.M I think OP did not see `~Foo:Bar` because `~Foo:Bar` is invoked after the execution of `cin.get();` and immediate before the end of the program. – cpplearner Jul 25 '17 at 04:42
  • @M.M: Note the `cin.get()` call. If the user didn't press anything, then there will be no output, since `main` never ended. – Nicol Bolas Jul 25 '17 at 04:43
  • Seems plausible. Anyway, I think the MSVC behaviour is bugged . Discussed here, https://stackoverflow.com/questions/35313292/aggregate-reference-member-and-temporary-lifetime – M.M Jul 25 '17 at 04:44

2 Answers2

5

Baz is a class with a constructor. Therefore, when you use list initialization, the compiler will look for a constructor to call. That constructor will be passed the members of the braced-init-list, or a std::initializer_list if one matches the members of the list. In either case, the rules of temporary binding to function parameters are in effect ([class.temporary]/6.1):

A temporary object bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

However, Bar is not a class with a constructor; it is an aggregate. Therefore, when you use list initialization, you (in this case) invoke aggregate initialization. And therefore, the member reference will be bound to the given prvalue directly. The rule for that is ([class.temporary]/6):

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:

Followed by 3 exceptions which do not apply to this case (including 6.1, quoted above).

The lifetime of the reference Bar::foo extends to the end of main. Which doesn't happen until cin.get() returns.


How can I implement the constructor of Baz correctly?

If by "correctly", you mean "like Bar", you cannot. Aggregates get to do things that non-aggregates can't; this is one of them.

It's similar to this:

struct Types { int i; float f };
using Tpl = std::tuple<int, float>;

int &&i1 = Types{1, 1.5}.i;
int &&i2 = std::get<0>(Tpl{1, 1.5});

i2 is a dangling reference to a subobject of a destroyed temporary. i1 is a reference to a subobject of a temporary whose lifetime was extended.

There are some things you just can't do through functions.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
0

To point out the difference between VC++ and g++, I think the below have some information on it https://stackoverflow.com/a/26581337/4669663

Since the move constructor is added by default, this

Foo:Bar
~Foo:Bar - Move constructor generated by VC
Foo:Baz
~Foo:Baz

"Rvalue references v3.0" adds new rules to automatically generate move constructors and move assignment operators under certain conditions. This is implemented in Visual Studio 2015.

Randolf
  • 125
  • 1
  • 9
  • There are no move operations in this code, therefore no invocation of move-constructors. (Unless the compiler is bugged to create a temporary here where the standard doesn't permit it , or something like that) – M.M Jul 25 '17 at 05:01