This is a code example from the C++20 spec ([basic.life]/8):
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
int main() {
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
}
Would the following be legal or undefined behavior:
struct C {
int& i; // <= the field is now a reference
void foo(const C& other) {
if ( this != &other ) {
this->~C();
new (this) C(other);
}
}
};
int main() {
int i = 3, j = 5;
C c1 {.i = i};
std::cout << c1.i << std::endl;
C c2 {.i = j};
c1.foo(c2);
std::cout << c1.i << std::endl;
}
In case it is illegal, would std::launder
make it legal? where should it be added?
Note: p0532r0 (page 5) uses launder for a similar case.
In case it is legal, how can it work without "Pointer optimization barrier" (i.e. std::launder
)? how do we avoid the compiler from caching the value of c1.i
?
The question relates to an old ISO thread regarding Implementability of std::optional
.
The question applies also, quite similarly, to a constant field (i.e. if above i
in struct C
is: const int i
).
EDIT
It seems, as @Language Lawyer points out in an answer below, that the rules have been changed in C++20, in response to RU007/US042 NB comments.
C++17 Specifications [ptr.launder] (§ 21.6.4.4): --emphasis mine--
[ Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless the type contains const or reference members; in the latter cases, this function can be used to obtain a usable pointer to the new object. ...— end note ]
C++17 [ptr.launder] code example in the spec (§ 21.6.4.5):
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5}; // p does not point to new object (6.8) because X::n is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
C++20 [ptr.launder] Specifications (§ 17.6.4.5):
[ Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless its complete object is a const object or it is a base class subobject; in the latter cases, this function can be used to obtain a usable pointer to the new object. ...— end note ]
Note that the part:
unless the type contains const or reference members;
that appeared in C++17 was removed in C++20, and the example was changed accordingly.
C++20 [ptr.launder] code example in the spec (§ 17.6.4.6):
struct X { int n; };
const X *p = new const X{3};
const int a = p->n;
new (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life])
// because its type is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
Thus, apparently the code in question is legal in C++20 as is, while with C++17 it requires using std::launder
when accessing the new object.
Open Questions:
What is the case of such code in C++14 or before (when
std::launder
didn't exist)? Probably it is UB - this is whystd::launder
was brought to the game, right?If in C++20 we do not need
std::launder
for such a case, how the compiler can understand that the reference is being manipulated without our help (i.e. without "Pointer optimization barrier") to avoid caching of the reference value?
Similar questions here, here, here and here got contradicting answers, some see that as a valid syntax but advise to rewrite it. I'm focusing on the validity of the syntax and the need (yes or no) for std::launder
, in the different C++ versions.