RETRACTION: The argument is erroneous. The proof of Lemma 2 relies on a hidden premise that the alignment of an aggregate type is determined strictly by the alignments of its member types. As Dyp points out in the commentary, that premise is not supported by the standard. It is therefore admissible for struct { Foo f }
to have a more strict alignment requirement that Foo
.
I'll play devil's advocate here, since no one else seems to be willing. I will argue that standard C++ (
I'll refer to N3797 herein) guarantees that
sizeof(T) == sizeof(U)
when
T
is a standard layout class (9/7) with default alignment having a single default-aligned non-static data member
U
, e.g,
struct T { // or class, or union
U u;
};
It's well-established that:
sizeof(T) >= sizeof(U)
offsetof(T, u) == 0
(9.2/19)
U
must be a standard layout type for T
to be a standard layout class
u
has a representation consisting of exactly sizeof(U)
contiguous bytes of memory (1.8/5)
Together these facts imply that the first sizeof(U)
bytes of the representation of T
are occupied by the representation of u
. If sizeof(T) > sizeof(U)
, the excess bytes must then be tail padding: unused padding bytes inserted after the representation of u
in T
.
The argument is, in short:
- The standard details the circumstances in which an implementation may add padding to a standard-layout class,
- None of those cirumstances applies in this particular instance, and hence
- A conforming implementation may not add padding.
Potential Sources of Padding
Under what circumstances does the standard allow an implementation to add such padding to the representation of a standard layout class? When it's necessary for alignment: per 3.11/1, "An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated." There are two mentions of adding padding, both for alignment reasons:
5.3.3 Sizeof [expr.sizeof]/2 states "When applied to a reference or a reference type, the result is the size of the referenced type. When applied
to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. The size of a most derived class shall be greater than zero (1.8). The result of applying sizeof
to a base class subobject is the size of the base class type.77 When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element."
9.2 Class members [class.mem]/13 states "Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1)."
(Notably the C++ standard does not contain a blanket statement allowing implementations to insert padding in structures as in the C standards, e.g., N1570 (C11-ish) §6.7.2.1/15 "There may be unnamed padding within a structure object, but not at its beginning." and /17 "There may be unnamed padding at the end of a structure or union.")
Clearly the text of 9.2 doesn't apply to our problem, since (a) T
has only one member and thus no "adjacent members", and (b) T
is standard layout and hence has no virtual functions or virtual base classes (per 9/7). Demonstrating that 5.3.3/2 doesn't allow padding in our problem is more challenging.
Some Prerequisites
Lemma 1: For any type W
with default alignment, alignof(W)
divides sizeof(W)
: By 5.3.3/2, the size of an array of n elements of type W
is exactly n times sizeof(W)
(i.e., there is no "external" padding between array elements). The addresses of consecutive array elements are then sizeof(W)
bytes apart. By the definition of alignment, it must then be that alignof(W)
divides sizeof(W)
.
Lemma 2: The alignment alignof(W)
of a default-aligned standard layout class W
with only default-aligned data members is the least common multiple LCM(W)
of the alignments of the data members (or 1 if there are none): Given an address at which an object of W
can be allocated, the address LCM(W)
bytes away must also be appropriately aligned: the difference between the addresses of member subobjects would also be LCM(W)
bytes, and the alignment of each such member subobject divides LCM(W)
. Per the definition of alignment in 3.11/1, we have that alignof(W)
divides LCM(W)
. Any whole number of bytes n < LCM(W)
must not be divisible by the alignment of some member v
of W
, so an address that is only n
bytes away from an address at which an object of W
can be allocated is consequently not appropriately aligned for an object of W
, i.e., alignof(W) >= LCM(W)
. Given that alignof(W)
divides LCM(W)
and alignof(W) >= LCM(W)
, we have alignof(W) == LCM(W)
.
Conclusion
Application of this lemma to the original problem has the immediate consequence that alignof(T) == alignof(U)
. So how much padding might be "required for placing objects of that type in an array"? None. Since alignof(T) == alignof(U)
by the second lemma, and alignof(U)
divides sizeof(U)
by the first, it must be that alignof(T)
divides sizeof(U)
so zero bytes of padding are required to place objects of type T
in an array.
Since all possible sources of padding bytes have been eliminated, an implementation may not add padding to T
and we have sizeof(T) == sizeof(U)
as required.