Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save schaumb/641d1b80f60ff5f699ba0b045f143e3e to your computer and use it in GitHub Desktop.
Save schaumb/641d1b80f60ff5f699ba0b045f143e3e to your computer and use it in GitHub Desktop.
Working around offsetof limitations in C++

Working around offsetof limitations in C++:

There is sometimes a situation in which one needs to get the relative offset of a structure field, common examples of this include serialization frameworks which aid to serialize objects, vertex attributes for rendering (D3D, GL.), etc.

The most common technique for getting this information is through the offsetof macro defined in stddef.h. Unfortunately using the macro in C++ comes with a new set of restrictions that prevent some (subjectively valid) uses of it.

Intuition would have you believe that using offsetof on classes/structures with standard-layout would work. However when a base class has data members, the deriving class is not considered standard-layout. Presumably the reason for this is that C++ wants to beable to permit very unusual layouts where a given inherited class might not have the same offset in all instances?

A less relaxed compiler will emit diagnostic for this as a result:

struct vec2 { float x, y; };
struct vec3 : vec2 { float z; };
struct vec4 : vec3 { float w; };

y_offset = offsetof(vec4, y);

Even though vec4 definitely has the following memory layout:

  0   4   8   C
| x | y | z | w |

This becomes a problem and often leads to techniques that lead to illegal code such as the following technique present in many game engines:

vec4 *v = 0;
y_offset = &v->y;

Various people would have you believe it's not undefined since it tends to be the common technique implored to implement the offsetof macro. Modern compilers do provide intrinsics however; (GCC, Clang) now use __builtin_offsetof because they're now beginning to optimize based on the assumption code does not depend on undefined behavior. For more information on why you should no longer write code like that, check out these in depth analysis of the technique:

https://software.intel.com/en-us/blogs/2015/04/20/null-pointer-dereferencing-causes-undefined-behavior

http://www.viva64.com/en/b/0306/

This leaves us in a tough position however. How can we work around this limitation without invoking undefined behavior?

One technique would be to create an object on stack and calculate the offset like so:

vec4 v;
v_offset = size_t(&v.y) - size_t(&v);

However this is not a valid analog for offsetof which is usuable in constant expressions as it yields a constant value. Which means code like the following:

enum { memory_needed = offsetof(vec4, y) };
unsigned char memory_for_x_and_y[memory_needed];

Cannot be realized with this technique.

One option would be to maintain a set of structures with the same layout without any inheritance but that leads to a maintenance hell. So instead I decided to explore C++11s constexpr to see if there was a way to accomplish this.

After a little bit of work, I came up with the following solution:

#pragma pack(push, 1)
template <typename Member, std::size_t O>
struct Pad {
    char pad[O];
    Member m;
};
#pragma pack(pop)

template<typename Member>
struct Pad<Member, 0> {
    Member m;
};

template <typename Base, typename Member, std::size_t O>
struct MakeUnion {
    union U {
        char c;
        Base base;
        Pad<Member, O> pad;
        constexpr U() noexcept : c{} {};
    };
    constexpr static U u {};
};


template<class Member, class Base>
std::tuple<Member, Base> get_types(Member Base::*);

template<class Member, class Base>
constexpr const Member& invoke(Member Base::* fun, const Base* obj) {
    return obj->*fun;
}

template<class IC, class Base, std::ptrdiff_t off = 0,
         class T = decltype(get_types(std::declval<typename IC::value_type>())),
         class Member = typename std::tuple_element<0, T>::type,
         class Orig = typename std::tuple_element<1, T>::type,
         bool = (&invoke(IC::value, static_cast<const Orig*>(&MakeUnion<Base, Member, off>::u.base)) > 
                                                             &MakeUnion<Base, Member, off>::u.pad.m)>
struct offset_of_impl {
    constexpr static std::ptrdiff_t value = offset_of_impl<IC, Base, off + 
        (sizeof(Member) < alignof(Orig) ? sizeof(Member) : alignof(Orig))>::value;
};

template<class IC, class TheBase, std::ptrdiff_t off,
         class T, class Member, class Orig>
struct offset_of_impl<IC, TheBase, off, T, Member, Orig, false> {
    constexpr static std::ptrdiff_t value = off;
};

#define offset_of(type, member) offset_of_impl<std::integral_constant<decltype(&type::member), &type::member>, type>::value
@schaumb
Copy link
Author

schaumb commented Mar 16, 2023

It's working C++11 and with old compilers too.

If you want to make to work with private fields:

 
template<class Member, class Base>
std::tuple<Member, Base> get_types(const Member& (*)(const Base&));


template<class Member, class Base>
constexpr const Member& invoke(const Member& (*fun)(const Base&), const Base* obj) {
    return fun(*obj);
}


template<class Base, class Result>
constexpr Result get_type(Result (*)(const Base&));

#define offset_of_priv(type, member) \
    offset_of_impl<std::integral_constant< \
        decltype(get_type<type>(&access_private::member)) (*) (const type&), \
        &access_private::member>\
    , type>::value
    
    
/* ... */


class B {
    int a;
    int b;
};

// from  https://github.com/martong/access_private with constexpr functions.
ACCESS_PRIVATE_FIELD(B, int, a)
ACCESS_PRIVATE_FIELD(B, int, b)


static_assert(offset_of_priv(B, a) == 0, "");
static_assert(offset_of_priv(B, b) == sizeof(int), "");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment