I call the undead clause the C++ rule that after the destruction of an object, if a new object is created at the same address, it can sometimes be considered the same object as the old one. That rule always existed in C++ but with some changes on the additional conditions.
I was made to read the latest undead clause by this question. The revised conditions in Lifetime [basic.life]/8 are:
(8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and
Well, duh. An object at a different address would not be the same object.
(8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
Again, duh.
(8.4) neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
It cannot a base class, classic (or a member with a special declaration that makes its address not unique). Again, duh.
(8.3) the original object is neither a complete object that is const-qualified nor a subobject of such an object, and
Now that's interesting. The object being replaced can't be either:
On the other hand, the object being resurrected can be:
So it seems to me that all of these objects x
can be resurrected:
Const member subobject
struct CI {
const int x;
};
CI s = { 1 };
new ((void*)&s.x) int(2);
int r = s.x; // OK, 2
Subobject of const member:
struct T {
int x;
};
struct CT {
const T m = { 1 };
};
CT s;
new ((void*)&s.m.x) int (2);
int r = s.m.x;
Element in an array of const objects:
const int x[1] = { 1 };
new ((void*)&x[0]) int (2);
int r = x[0];
Also object of class type with const or references members do not seem to be prohibited; the resurrected object is still called x
.
Class with a const member:
struct CIM {
CIM(int i): m(i) {}
const int m;
};
CIM x(1);
new ((void*)&x) CIM(2);
int r = x.m; // OK, 2
Class with a reference member:
struct CRM {
CRM (int &r): m(r) {}
int &m;
};
int i=1,j=2;
CRM x(i);
new ((void*)&x) CRM(j);
int r = x.m; // OK, 2
Note: I added the bonus later because putting constants in ROM came up in the discussion.
It would be surprising if all requirement of the standard related to object life-time were not in [basic-life].
There are few chances that the "complete" adjective has been inadvertedly added to the name "object" in the standard paragraph you cite.
In the paper P0137, one can read this rational (paper cited in @LanguageLawyer comment below):
This is necessary to allow types such as std::optional to contain const subobjects; the existing restriction exists to allow ROMability, and so only affects complete objects.
To reassure us, we can verify that compilers do follow the standard wording at the letter: they perform constant optimization for complete const objects but not for const member suboject of non const complete objects:
Let's consider this code:
struct A{const int m;};
void f(const int& a);
auto g(){
const int x=12;
f(x);
return x;
}
auto h(){
A a{12};
f(a.m);
return a.m;
}
Both Clang and GCC generates this assembly when targeting x86_64:
g(): # @g()
push rax
mov dword ptr [rsp + 4], 12
lea rdi, [rsp + 4]
call f(int const&)
mov eax, 12 ;//the return cannot be anything else than 12
pop rcx
ret
h(): # @h()
push rax
mov dword ptr [rsp], 12
mov rdi, rsp
call f(int const&)
mov eax, dword ptr [rsp] //the content of a.m is returned
pop rcx
ret
The returned value is placed in register eax
(according to the ABI specification: System V x86 processor specific ABI):
In the function g
the compiler is free to suppose that x
can not be changed accross the call to f
because x
is a complete const object. So the value 12
is placed directly in the eax
register as an immediate value: mov eax, 12
.
In the function h
the compiler is not free to suppose that a.m
can not be changed accross the call to f
because a.m
is not a suboject of a complete const object. So after the call to f
the value of a.m
must be loaded from memory to eax
: mov eax, dword ptr [rsp]
.
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