Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize rvalue reference member

Tags:

c++

I am trying to initialize a rvalue reference member in the following situations: struct A is a aggregate and class B has a user defined constructor so it is not an aggregate. According to cppreference here,

struct A {
  int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference

The reference in my struct A should be correctly initialized and the temporary string should be extended, but it's not.
For my class B, in the same cppreference page:

a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696). (until C++14)

But I am still getting problem with MSVC with /std:c++latest. Am I missing anything?

struct A
{
    std::string&& ref;
};

class B
{
    std::string&& ref;
public:
    B(std::string&& rref):ref(std::move(rref)){}
    void print() {std::cout<<ref<<'\n';}
};

int main()
{
    A a{ std::string{"hello world"} };
    std::cout<<a.ref<<'\n'; //garbage in MSVC with c++latest, correct in GCC9.2
    B b{ std::string{"hello world"} };
    b.print(); //same
}

EDIT: Please tell me if I am getting dangling reference in these two cases in the first place. And for MSVC, the version is the latest update, v19.24.28315

EDIT2: OK I am actually confused by cppreference's explanation. I am not asking specificly for C++20. Please tell me under which C++ version, the code is well-formed or not.

EDIT3: Is there a proper way to initialize a rvalue reference member of a non-aggregate class? Since bind it to a temporary in a member initializer list is ill-formed (until C++14, does it mean it is good since C++14?) and passing a temporary to a constructor expecting an rvalue cannot extend its lifetime twice.

like image 544
sz ppeter Avatar asked Feb 21 '26 07:02

sz ppeter


1 Answers

Your class A is an aggregate type, but B isn't, because it has a user-provided constructor and a private non-static member.

Therefore A a{ std::string{"hello world"} }; is aggregate initialization which does extend the lifetime of the temporary through reference binding to that of a.

On the other hand B is not aggregate initialization. It calls the user-provided constructor, which passes on the reference. Passing on references does not extend the lifetime of the temporary. The std::string object will be destroyed when the constructor of B exits.

Therefore the later use of a has well-defined behavior, while that of b will have undefined behavior.

This holds for all C++ versions since C++11 (before that the program would be obviously ill-formed).

If your compiler is printing garbage for a (after removing b from the program so that it doesn't have undefined behavior anymore), then this is a bug in the compiler.


Regarding edit of question:

There is no way to extend the lifetime of a temporary through binding to a reference member of a non-aggregate class.

Relying on this lifetime extension at all is very dangerous, since you would likely not get any error or warning if you happen to make the class non-aggregate in the future.

If you want the class to always retain the object until its lifetime ends, then just store it by-value instead of by-reference:

class B
{
    std::string str;
public:
    B(std::string str) : str(std::move(str)) {}
    void print() { std::cout << str << '\n'; }
};
like image 121
walnut Avatar answered Feb 22 '26 20:02

walnut



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!