Consider the following three struct
s:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
On typical platforms where int
is 4 bytes, the sizes, alignments and total padding1 are as follows:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
There is no overlap between the storage of the blub
and blob
members, even though the size 1 blob
could in principle "fit" in the padding of blub
.
C++20 introduces the no_unique_address
attribute, which allows adjacent empty members to share the same address. It also explicitly allows the scenario described above of using padding of one member to store another. From cppreference (emphasis mine):
Indicates that this data member need not have an address distinct from all other non-static data members of its class. This means that if the member has an empty type (e.g. stateless Allocator), the compiler may optimise it to occupy no space, just like if it were an empty base. If the member is not empty, any tail padding in it may be also reused to store other data members.
Indeed, if we use this attribute on blub b0
, the size of bla
drops to 8
, so the blob
is indeed stored in the blub
as seen on godbolt.
Finally, we get to my question:
What text in the standards (C++11 through C++20) prevents this overlapping without no_unique_address
, for objects that are not trivially copyable?
I need to exclude trivially copyable (TC) objects from the above, because for TC objects, it is allowed to std::memcpy
from one object to another, including member subobjects, and if the storage was overlapped this would break (because all or part of the storage for the adjacent member would be overwritten)2.
1 We calculate padding simply as the difference between the structure size and the size of all its constituent members, recursively.
2 This is why I have copy constructors defined: to make blub
and blob
not trivially copyable.