The goal of this article is to use reflection, to write a template function, in order to determine if the template type parameter is containing padding.

We will see, how I first took a way to complex road, but I decided to keep it in the article, since it allows to use a few more reflection functions, and then we switch to the more simple solution.

Once I had the initial implementation, not supporting inheritance, I had a realization the next day while driving my car. It was to complex, there is an easier solution.

And that solution will make it even easier to support inheritance.

Fyi, the list of previous articles in this series:

The design of the initial algorithm

In this mechanism we make a distinction between internal padding and padding at the end of the structure (trailing padding).

Internal padding is present, if the “offset of member(i + 1) is greater than the “offset of member(i) + the size of member(i)”.

This is something, to check for every member, except the first one.

Padding at the end, is present when the “offset of the last member + the size of the last member” is less than the size of the entire type.

So things we need, in order to pull this of:

  • iterate over all the members
  • knowing when we are the first/last member
  • offset of a member
  • size of a member
  • size of the type

The implementation of the initial algorithm

All of the things mentioned above, can be answered by reflection (size of the type does not need reflection):

  • iterating over all the members: that’s what we do by the “template for” over all the non static members ‘nonstatic_data_members_of(refl, ctx)’
  • see my first article on the ‘define_static_array‘ construct
  • we keep a counter, counting the current number of members processed
  • we also keep track of the “anticipated offset of the next member : the offset of the current member + the size of the current member”
  • for the first member, as mentioned, no special checks are needed
  • in case it was neither the first, nor the last member, we do a check: if the anticipated offset < offset current member (offset_of(member).bytes) ==> padding
  • and in case it would be the last member, we also check if it’s (offset + size) < size of the struct
  • the moment we spot padding, we are done, and return true
  • if we make it till the last statement of the function, there is no padding and we return false

That spells out in code like this:

We can use it for the following test cases:

Using it like this: (note also the constexpr compile time use via static_assert)

This gives the following output:

true
true
true
false
false

This implementation should work for simple structs, but does not support inheritance.

Time to switch to the simple algorithm.

The simple algorithm

And then it struck me, another way to check if we have padding (we will loop over all members in this case, no early bailing out), fingers crossed this is a correct algorithm …

sum up the sizes of all the members, if this sum < sizeof(T) ==> padding

It is a simple as that, so ingredients needed:

  • loop over the members
  • size_of(member)
  • sizeof(T)

No need anymore for:

  • offset_of(member)
  • tracking the index of the member

Before we look at the code, let’s solve the inheritance feature.

Supporting inheritance

The naive solution, check if the bases have padding, if any does, problem solved, if not, do as before for the type T under inspection (as if there was no inheritance).

I say this is a naive solution, because it can fail. There is something like “Tail Padding Reuse“. And it comes with a little catch itself, it can not be applied to an old fashioned POD, in order to allow memcpy applications.

That’s why in our test types, we create a type with a private member, so it is no longer a POD.

So what is “Tail Padding Reuse”

If you have a type which has trailing padding, that padding could be filled up with the first members of the derived class/struct, assuming they fit (size and alignment wise).

So in the following example (assumptions of size of the types in comments), there will be no padding.

The result of this is that the naive solution does not work, however the base principle of adding the sizes of ALL members (base and derived) and comparing that to the sizeof(T => being Derived) still works.

This means, we need to loop (recursively) over the base classes of our type (bases_of()), and get the sum of their member sizes.

For that purpose we add a little helper template method, which accumulates the member sizes, recursively.

Note, that the moment we want to call the helper method recursively on a base class, we need the type of that base class, but in the code domain, that’s why we need to splice it back from the reflection domain to the code domain:

typename [: type_of(base) :]

The real user function, calls this helper and compares it to the sizeof(T).

Let’s create some test type with inheritance:

We use our checker as follows (which also still works ok, on the first batch of test types):

This gives the following output:

true
true

true
true

true
false
true
false

You can play with the code online here: https://godbolt.org/z/c8of9MMYz

Conclusion

We have implemented a new type trait, to check if a type has padding or not. Most probably there might be types for which this fails, probably types with bit sets, several members being set of bits, into a bigger integer (member).

That’s future work, to check and fix if needed. The real application is for simple data structures, eg to find out if there is padding, and to act accordingly on this fact (eg. (de)serialization).


Leave a Reply

Your email address will not be published. Required fields are marked *