David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore, the member value is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase) whereas in FooAfter, the member value is placed at offset 12 (effectively using FooAfterBase's tail-padding).
It is clear to me that FooBeforeBase is standard-layout, but FooAfterBase is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase's padding, ending up with sizeof(FooAfter) == 16. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBaseis standard-layoutFooBeforeis not (both it and a base class have non-static data members, similar toEin this example)FooAfterBaseis not (it has non-static data members of differing access)FooAfteris not (for both of the above reasons)