11

I would like to get the offset of a standard layout member variable when provided with a poiner to that variable. I cannot use offsetof since I have a pointer and not a name. The current code I have looks something like this, and I am wondering if there is a standards compliant way to get rid of the dummy variable.

template<class T>
struct {
  ptrdiff_t get_offset( int (T::*mem) )
  {
    T dummy;
    return reinterpret_cast<char*>(&(dummy.*mem)) 
      - reinterpret_cast<char*>(&dummy);
  }
};

This function should only be callable with int member variable points (this is intentional).

I am quite certain that the compiler doesn't actually create the dummy variable but it'd still be nice if I could get rid of it. I can't use a null pointer since dereferencing null is not defined (though it probably works on all common compilers). A C++03 solution would be good, or a C++11 solution is also of interest (but not usable by me now).

NOTE: I'm already aware this is only standards compliant is T is a standard layout type.

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • The return type should be `ptrdiff_t`, I suppose, and you should use `std::distance`. And the function should be `static`. – Kerrek SB Aug 27 '12 at 11:56
  • @KerrekSB, yes. I compile with full/extra warnings in GCC but I guess `size_t == ptrdiff_t`, so no warnings. – edA-qa mort-ora-y Aug 27 '12 at 11:57
  • `ptrdiff_t` is signed... anyway, the `dummy` should also be static I suppose. – Kerrek SB Aug 27 '12 at 11:59
  • @KerrekSB: Why `std::distance`? The subtraction and `std::distance` are the same here (§24.4.4/4). – kennytm Aug 27 '12 at 12:01
  • @KerrekSB, function is not static in this case since my full function is not just a utility function but a true member function. I'd also avoid `static T dummy` since that might trip up compiler optimizations (as static often must be handled diffently, though here we might still be okay) – edA-qa mort-ora-y Aug 27 '12 at 12:05
  • The implementation of `offsetof` macro involves dereferencing null pointer, btw – Andriy Aug 27 '12 at 12:05
  • 2
    @Andrey, but that's an implementation detail and could be different in each compiler. – edA-qa mort-ora-y Aug 27 '12 at 12:06
  • 1
    Another small improvement would be to use something like [Boost.AddressOf](http://www.boost.org/doc/libs/release/libs/utility/utility.htm#addressof), to ensure that this works for types with overloaded `operator&`. – Mankarse Aug 27 '12 at 12:15
  • @Andrey: Not in gcc. http://stackoverflow.com/questions/400116/what-is-the-purpose-and-return-type-of-the-builtin-offsetof-operator – kennytm Aug 27 '12 at 14:15

3 Answers3

9

How about:

template<class T>
struct {
  ptrdiff_t get_offset( int (T::*mem) )
  {
    union {
      int used;
      T unused;
    } dummy;
    return reinterpret_cast<char*>(&(dummy.unused.*mem)) 
      - reinterpret_cast<char*>(&dummy.unused);
  }
};

The address of a union member doesn't depend on the union member being constructed. Works already in C++03, but then only for PODs.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • So this version bascially eliminates any call to `T::T()` and `T:~T()`. It still has a dummy, but the abstract machine executes less code. – edA-qa mort-ora-y Aug 27 '12 at 14:53
  • Well, obviously you need the memory, else you can't form the pointers. – MSalters Aug 27 '12 at 15:01
  • 1
    This is still UB, as you are using a component of a `union` that was not active at the point of use. As such, how is it better than `reinterpret_cast(nullptr)`? It is worse in that you end up creating a bunch of stack room for the `T`. There could be a system on which this works but the `nullptr` does not, but the other way around could be true as well. – Yakk - Adam Nevraumont Mar 09 '14 at 17:05
  • @Yakk: Which exact bit would be "using a component of the union that's not active"? Remember that I may (must) form an lvalue to an inactive member in order to make it active. I.e. `dummy.unused = T()` writes to an inactive member, to make it active. I can't _read_ from an inactive member. – MSalters Mar 10 '14 at 10:24
  • @msalters `(dummy.unused.*mem` -- I would be suspicious of that. I should standard delve before condemning it, but... do you have a justification why it is defined behaviour? – Yakk - Adam Nevraumont Mar 10 '14 at 12:12
  • 2
    Well, that's a perfectly reasonable lvalue to which you can write a new integer, which of course would make `unused` used/active. Deep down, the thing which breaks when reading inactive union members is the `lvalue-to-rvalue conversion` (or `glvalue-to-prvalue conversion` per footnote 53). But we don't have that conversion. – MSalters Mar 10 '14 at 12:29
2

I'm afraid that no standard-compliant solution which satisfies OP requirements exists.

I can give a couple of non-compliant ones.

template<class T>
  size_t get_offset( int (T::*mem) )
    {
    return reinterpret_cast<char*>(&(((T*)nullptr)->*mem))-reinterpret_cast<char*>(nullptr);
    }

It's funny, but the following works in VC2010, making use of offsetof being a macro.

template<class T>
  size_t get_offset( int (T::*mem) )
    {
    return offsetof(T, *mem);
    }
Andriy
  • 8,486
  • 3
  • 27
  • 51
  • You're dereferencing a null pointer via the `->*` operator. Consider the equivalent `(*(T*)nullptr).*mem` – edA-qa mort-ora-y Aug 27 '12 at 12:07
  • You're still dereferencing a null pointer. Just because `offsetof` does it, doesn't mean that it's not UB. – Peter Alexander Aug 27 '12 at 12:07
  • @PeterAlexander: Yes, I know... I'm afraid that no solution which satisfies OP requirements exist. Either a dummy variable or dereferencing NULL is needed. – Andriy Aug 27 '12 at 12:10
  • @Andrey, you can put "No" as an answer and if nobody else finds a solution I have to accept "No". I already suspect there is no other standards compliant solution. – edA-qa mort-ora-y Aug 27 '12 at 12:32
1

So how about:

template<class T>
struct {
    ptrdiff_t get_offset( int (T::*mem) )
    {
        return 
        ( &reinterpret_cast<const char&>( 
            reinterpret_cast<const T*>( 1 )->*mem ) 
          - reinterpret_cast<const char*>( 1 )      );
    }
};

..?

This avoids both using a dummy AND dereferencing null. It works on all the compilers I've tried. The cast to char reference and then take the address (rather than take the address and then cast to char pointer) may seem unusual but it avoids a warning/error on some compilers.

Rod
  • 11
  • 2