- Only the outer type can know the offset to the inner member sub-object
- Therefore the member sub-object type needs to know about the outer type
- Luckily, this sort of thing comes up fairly frequently. Indeed, the frequency with which it recurs could be considered ... curious.
- Unfortunately, no-one has come up with a good name for it. So, it's called the Curiously Recurring Template Pattern or CRTP for short.
The hard thing is figuring out a way for the outer type to automate production of the inner offsets, without having to write each one by hand. Doing it by hand is easy, but tedious, eg.
// use an enum to create distinct types
template<typename Outer, typename Outer::FieldId ID>
struct Inner
{
static constexpr size_t offset();
};
struct Outer
{
enum FieldId { First, Second };
int header;
Inner<Outer, FieldId::First> first;
double interstitial;
Inner<Outer, FieldId::Second> second;
static constexpr size_t offset_of(std::integral_constant<FieldId, FieldId::First>) { return offsetof(Outer, first); }
static constexpr size_t offset_of(std::integral_constant<FieldId, FieldId::Second>) { return offsetof(Outer, second); }
};
template<typename Outer, typename Outer::FieldId ID>
constexpr size_t Inner<Outer, ID>::offset()
{
return Outer::offset_of(std::integral_constant<decltype(ID), ID> {});
}
This is clunky, partly because of the std::integral_constant
wrapper (which could be avoided or typedef'd), but mostly because the ID-to-field mapping has to be expressed manually in code.
Automating production is hard without compile-time reflection. You can automate everything if you just use a tuple-like object instead of a struct at the top level, but that makes it harder to interleave "smart" and dumb members, and probably changes the layout, and it definitely breaks the StandardLayoutType requirements, which may prevent offsetof
from working entirely.