问题
The "undead" clause
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:
- a complete const object
- part of a complete const object
On the other hand, the object being resurrected can be:
- a const member subobject
- a subobject of such const member
- an element in an array of const objects
Const subobject
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];
Classes with const and reference members
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
The questions
- Is that interpretation of the clause correct?
- If so, is there any other clause that forbid these overwriting operations?
- If so, is that intended? Why was that changed?
- Is that a breaking change for code generators? Do all compilers really support that? Don't they optimize based on const members, const elements of arrays being immutable and references not being reboundable?
- BONUS QUESTION: does that affect ROM-ability of const objects with adequate storage class (not dynamically created objects, of course) and adequate initialize?
Note: I added the bonus later because putting constants in ROM came up in the discussion.
回答1:
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 thatx
can not be changed accross the call tof
becausex
is a complete const object. So the value12
is placed directly in theeax
register as an immediate value:mov eax, 12
.In the function
h
the compiler is not free to suppose thata.m
can not be changed accross the call tof
becausea.m
is not a suboject of a complete const object. So after the call tof
the value ofa.m
must be loaded from memory toeax
:mov eax, dword ptr [rsp]
.
来源:https://stackoverflow.com/questions/59298904/undead-objects-basic-life-8-why-is-reference-rebinding-and-const-modificat