18

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.

Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
  • I'm trying to figure out where in the standard that it says that this does what you expect it does. But I can't find it. – Nicol Bolas Oct 10 '12 at 04:31
  • 4
    Whats wrong with the standard [`offsetof`](http://en.cppreference.com/w/cpp/types/offsetof)? – Some programmer dude Oct 10 '12 at 06:13
  • @NicolBolas I guess it doesn't. Shouldn't the dereference of a `nullptr` (and I think `->` counts as dereference) be UB already? But then again, VC's version of the `offsetof` macro isn't any different. So in practice it's probably rather implementation defined than undefined. – Christian Rau Oct 10 '12 at 07:29
  • I thought a `constexpr` function isn't allowed to contain `reinterpret_cast`s or pointer derefs and address operators? – Christian Rau Oct 10 '12 at 07:32
  • 1
    @ChristianRau If the standard doesn't require implementations to document their support, the support isn't implementation-defined. In this case, if an implementation defines the behaviour (and I'm not saying VC does), it's simply a permitted extension. A valid consequence of undefined behaviour is doing exactly what you want. –  Oct 10 '12 at 07:52
  • @hvd The *"implementation-defined"* from my comment was meant in a more informal sense (as introduced by the preceding *"in practice"*). Of course it is and will always be UB and not IB, nobody argues about that. – Christian Rau Oct 10 '12 at 08:43
  • @ChristianRau Implementation-defined has a very specific meaning in C++; using it informally with a different meaning isn't wrong, but I did not get from your comment that you were knowingly doing so. It's actually still not clear to me what exactly you did mean: did you mean the VC compiler will not complain about null pointer dereferencing, or did you mean the VC compiler supports null pointer dereferencing? They differ in how safe it is to rely on the current behaviour. –  Oct 10 '12 at 08:49
  • 1
    @Joachim Pileborg: `offsetof` only works with member names, not with a `M T::*`. – Travis Gockel Oct 10 '12 at 13:17

1 Answers1

18

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.

Axalo
  • 2,953
  • 4
  • 25
  • 39
Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • 4
    A constant expression cannot involve a `reinterpret_cast` (unless not evaluated). – Luc Danton Oct 10 '12 at 09:20
  • 1
    @LucDanton: Thanks for the info. I also found a [defect report 1384](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1384) that talks about loosening the restrictions about that. – Jesse Good Oct 10 '12 at 09:43
  • I've been trying to modify this method to allow OFFSET_OF to accept a pointer to member without success (http://stackoverflow.com/questions/22359535/gcc-can-i-use-offsetof-with-templated-pointer-to-member) - does anyone know if it's possible? – Charlie Skilbeck Mar 12 '14 at 19:18
  • The made it more strict but both [gcc and clang provide a work-around](http://stackoverflow.com/questions/24398102/constexpr-and-initialization-of-a-static-const-void-pointer-with-reinterpret-cas/24400015#24400015) to use `reinterpret_cast` in a constant expression. – Shafik Yaghmour Aug 05 '14 at 20:48
  • So you believe your code does not invoke undefined behavior? – Shafik Yaghmour Aug 05 '14 at 20:50
  • @ShafikYaghmour: Thank you for the information. I do believe my code invokes UB. – Jesse Good Aug 05 '14 at 21:02