Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rvalue data member initialization: Aggregate initialization vs constructor

Tags:

c++

c++11

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.

like image 618
W.H Avatar asked Jul 25 '17 04:07

W.H


1 Answers

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.

like image 118
Nicol Bolas Avatar answered Oct 26 '22 23:10

Nicol Bolas