Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to reset reference to another value in C++?

I know generally it's impossible to reset a reference after it's already initialized.

However, I somehow try out the following code and it happens to work on both clang++ and g++.

My question is, is the following a valid (behavior-defined) C++?

std::string x = "x";
std::string y = "y";
std::string i = "i";
std::string j = "j";

// now references to x, y
std::pair<std::string &, std::string &> p { x, y };
p.first = "1"; //changes x
p.second = "2"; //changes y
// now references to i, j
new (&p) std::pair<std::string &, std::string &> {i, j};
p.first = "1"; //changes i
p.second = "2"; //changes j

The above code works on g++ and clang++, but is it good C++? Thanks.

like image 309
user534498 Avatar asked Apr 26 '18 09:04

user534498


People also ask

Can a reference value be reassigned?

Once defined, a reference cannot be reassigned because it is an alias to its target. What happens when you try to reassign a reference turns out to be the assignment of a new value to the target. Because arguments of a function are passed by value, a function call does not modify the actual values of the arguments.

Can reference be reset?

(C) Once a reference is created, it cannot be later made to reference another object; it cannot be reset. Explanation: We can create a constant reference that refers to a constant.

Can reference variable be changed?

Once a reference is established to a variable, you cannot change the reference to reference another variable.

Can reference be changed once initialized?

Explanation. A reference to T can be initialized with an object of type T , a function of type T , or an object implicitly convertible to T . Once initialized, a reference cannot be changed to refer to another object.


2 Answers

The snippet has undefined behaviour, but barely so. Conceptually, you destroyed the old references and created new ones, you didn't rebind the reference even if you reused the memory. This part is completely fine.

The catch is if the reused class contains const or reference members, then the original name of the variable cannot be used to refer to the new object

new (&p) std::pair<std::string &, std::string &> {i, j};
// p does not refer to the newly constructed object
p.first = "1";  // UB
p.second = "2"; // UB

The fix is simple, in this case

auto p2 = new (&p) std::pair<std::string&, std::string&> {i, j};
p2->first = "1";
p2->second = "2";

Another solution is the C++17 function std::launder

new (&p) std::pair<std::string &, std::string &> {i, j};
std::launder(&p)->first = "1";
std::launder(&p)->second = "2";

These rules presumably enables the compiler to make more optimizations around references and const members.

like image 154
Passer By Avatar answered Sep 28 '22 10:09

Passer By


My question is, is the following a valid (behavior-defined) C++?

It could be. The key here is that pair. You ended the lifetime of the pair object, and started the lifetime of another pair object in the same storage (the placement new does both of these things).

But you should be aware you aren't rebinding any references. You kill an object that held references, and create a new on in the same spot. Conceptually, you had two "old" references, and now two "new" ones.

Your code could be fine because the pair is a simple structure that holds a pair of references, and it would be valid if the pair held any trivially destructible types. If however the d'tor of any pair element is not trivial, you'll have undefined behavior. Because destructors will not be executed as part of placement new.

The problem as Passer By noted, is that you can't use p to refer to the "new object", because it holds references. That would be the cause of UB.

is it good C++?

That's debatable. It certainly is not something one would expect to see often.

like image 33
StoryTeller - Unslander Monica Avatar answered Sep 28 '22 09:09

StoryTeller - Unslander Monica