C++ Compile-Time offsetof inside a template
Asked Answered
W

1

20

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.

Wendel answered 10/10, 2012 at 2:56 Comment(8)
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.Cumulate
Whats wrong with the standard offsetof?Dismantle
@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.Osman
I thought a constexpr function isn't allowed to contain reinterpret_casts or pointer derefs and address operators?Osman
@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.Mckinney
@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.Osman
@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.Mckinney
@Joachim Pileborg: offsetof only works with member names, not with a M T::*.Wendel
I
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.

Ichthyic answered 10/10, 2012 at 5:0 Comment(6)
A constant expression cannot involve a reinterpret_cast (unless not evaluated).Whirlybird
@LucDanton: Thanks for the info. I also found a defect report 1384 that talks about loosening the restrictions about that.Ichthyic
I've been trying to modify this method to allow OFFSET_OF to accept a pointer to member without success (#22360035) - does anyone know if it's possible?Afterburning
The made it more strict but both gcc and clang provide a work-around to use reinterpret_cast in a constant expression.Immensurable
So you believe your code does not invoke undefined behavior?Immensurable
@ShafikYaghmour: Thank you for the information. I do believe my code invokes UB.Ichthyic

© 2022 - 2024 — McMap. All rights reserved.