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