Consider the following three structs:
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.