Passing the offset of a field as a template parameter to that field

607 Views Asked by At

What I am trying to do is to have a class which is aware of its offset within an enclosing class with no runtime overhead at all. Here's an example of what I wish I could do:

template<int offset>
struct Inner {
};

struct Outer {
   int placeholder;
   Inner<offsetof(Outer, ptr)> ptr; 
};

The above code doesn't compile because offsetof(Outer, ptr) doesn't know about ptr (it's helping define it). I have implemented a few versions of this same idea that do incur runtime overheads (both in memory and executed instructions), but I'm having trouble implementing a "0 runtime overhead" version like my dream implementation above. Any ideas how this can be done?

2

There are 2 best solutions below

1
Renat On

It could be simulated ugly way with the code duplication and a lot of boilerplate asserts as: declare a 'template' class with the same layout, declare target class, do a compile time assert that corresponding fields have the same offsets.

struct OuterTemplate {
   int placeholder;
   Inner<0> ptr; 
};

struct Outer {
   int placeholder;
   Inner<offsetof(OuterTemplate, ptr)> ptr; 
};

static_assert(offsetof(OuterTemplate, ptr) == offsetof(Outer, ptr), 
              "Same offsetof assumption is not working");
0
Useless On
  1. Only the outer type can know the offset to the inner member sub-object
  2. Therefore the member sub-object type needs to know about the outer type
  3. Luckily, this sort of thing comes up fairly frequently. Indeed, the frequency with which it recurs could be considered ... curious.
  4. 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.