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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With