C++ Compile-Time offsetof inside a template

Travis Gockel picture Travis Gockel · Oct 10, 2012 · Viewed 7.5k times · Source

I have the need to use offsetof from a template with a member selector. I've come up with a way, if you'll excuse the awkward syntax:

template <typename T,
          typename R,
          R T::*M
         >
constexpr std::size_t offset_of()
{
    return reinterpret_cast<std::size_t>(&(((T*)0)->*M));
};

Usage isn't perfect (annoying at best):

struct S
{
    int x;
    int y;
};

static_assert(offset_of<S, int, &S::x>() == 0, "");
static_assert(offset_of<S, int, &S::y>() == sizeof(int), "");

The non-constexpr form is easier to use:

template <typename T, typename R>
std::size_t offset_of(R T::*M)
{
    return reinterpret_cast<std::size_t>(&(((T*)0)->*M));
};

at the obvious disadvantage that it isn't done at compile-time (but easier to use):

int main()
{
    std::cout << offset_of(&S::x) << std::endl;
    std::cout << offset_of(&S::y) << std::endl;
}

What I'm looking for is syntax like the non-constexpr variety, but still fully compile-time; however, I can't come up with the syntax for it. I would also be happy with an offset_of<&S::x>::value (like the rest of the type traits), but can't figure out the syntax magic for it.

Answer

Jesse Good picture Jesse Good · Oct 10, 2012

The following should work (credits go to the answer to this question for the idea):

#include <cstddef>

template <typename T, typename M> M get_member_type(M T::*);
template <typename T, typename M> T get_class_type(M T::*);

template <typename T,
          typename R,
          R T::*M
         >
constexpr std::size_t offset_of()
{
    return reinterpret_cast<std::size_t>(&(((T*)0)->*M));
}

#define OFFSET_OF(m) offset_of<decltype(get_class_type(m)), \
                     decltype(get_member_type(m)), m>()

struct S
{
    int x;
    int y;
};

static_assert(OFFSET_OF(&S::x) == 0, "");

Note that in gcc, the offsetof macro expands to a builtin extension which can be used at compile time (see below). Also, your code invokes UB, it dereferences a null pointer, so even if it might work in practice, there are no guarantees.

#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)

As pointed out by Luc Danton, constant expressions cannot involve a reinterpret_cast according to the C++11 standard although currently gcc accepts the code (see the bug report here). Also, I found defect report 1384 which talks about making the rules less strict, so this might change in the future.