How to calculate offset of a class member at compile time?
Asked Answered
I

5

26

Given a class definition in C++

class A
{
  public:
    //methods definition
    ....

  private:
    int i;
    char *str;
    ....
}

Is it possible to calculate the offset of a class member at compile time using C++ template meta-programming? The class is not POD, and can have virtual methods, primitive and object data member.

Impure answered 1/11, 2012 at 15:59 Comment(9)
What exactly do you mean by the "offset of a class member"? Do you mean how many bytes you'd have to add to a pointer to an instance of the class (after, say, reinterpret_cast'ing it to char *) to get to the member? If so, wouldn't simple subtraction tell you?Ordinate
You could use offsetof(A, i) if that were defined for such types. Check your compiler documentation to see if it is.Burress
Here is the link to example code that uses offsetof().Extrorse
@Robᵩ and Hindol: Because of the extended functionality of structs in C++, in this language, the use of offsetof is restricted to "POD types", which for classes, more or less corresponds to the C concept of struct (although non-derived classes with only public non-virtual member functions and with no constructor and/or destructor would also qualify as POD).Crossley
@MuriloVasconcelos - Agreed, thus the subjunctive mood in my sentence, "if that were defined."Burress
@David Schwartz: Yes, this can be computed at runtime.Impure
@Robᵩ: if it's not defined, define it yourself ;)Chaos
I remember that the offsetof is not part of the standard, but still that's what you seem to look for.Facial
@MuriloVasconcelos and everyone, can you give an example of a class where offsetof would fail?Drachm
N
17

Based on Matthieu M.'s answer but shorter and with no macros:

template<typename T, typename U> constexpr size_t offsetOf(U T::*member)
{
    return (char*)&((T*)nullptr->*member) - (char*)nullptr;
}

And it's called like this:

struct X { int a, b, c, d; }

std::cout << "offset of c in X == " << offsetOf(&X::c);

Edit:

Jason Rice is correct. This will not produce an actual constant expression in C++11. It doesn't look possible given the restrictions in http://en.cppreference.com/w/cpp/language/constant_expression -- in particular no pointer difference and reinterpret_castcan be in a constant expression.

Napalm answered 22/11, 2013 at 9:20 Comment(7)
Just to spell it out, U is the member's type and T is the struct's type. I fudged those the first time.Tannertannery
@Glen Low: Do we really need the - (char*)nullptr part? Aren't we just subtracting zero? I guess we can make it shorter :)Fauna
@Fauna That seems to work on gcc but not llvm: error: cannot initialize return object of type 'size_t' (aka 'unsigned int') with an rvalue of type 'char *'Tannertannery
If fact, if i use the above to initialize a constexpr it does not compile with clang/llvm.Tannertannery
This causes undefined behaviour by dereferencing null pointer, and also by subtracting pointers when both pointers do not point to the same objectCooke
I don't recommend this solution. On Clang, when used with struct with multiple base classes it will give offset of member with respect to beginning of class where member is declared - this is wrong and unexpected. godbolt.org/z/qzExEd I suggest the use of dummy static object if possibleIssy
I just tried it and it's broken here on MSVC x64. Here's my code: godbolt.org/z/Y3nqeE3T5 . I'm getting an error error C2607: static assertion failed on the static_assert(offsetOf(&MyClass::Member2) == 0x4); line. The offsetOf always returns 0 no matter what, I know this because the program compiles with this static assert: static_assert(offsetOf(&MyClass::Member2) == 0x0);. Also, this doesn't even compile on Clang 17.0.1(latest at the moment of writing this comment) x86-64 with error: static assertion expression is not an integral constant expression.Drachm
C
7

Well... in C++11, you actually can compute such offsets right with regular C++ facilities (ie, without delegating to a particular compiler intrinsic).

template <typename T, typename U>
constexpr std::size_t func(T const& t, U T::* a) {
     return (char const*)&t - (char const*)&(t.*a);
}

However this relies on t being a reference to a constexpr instance here, which might not be applicable to all classes. It does not forbid T from having a virtual method though, nor even a constructor, as long as it is a constexpr constructor.

Still, this is quite a hindrance. In unevaluated contexts we could actually use std::declval<T>() to simulate having an object; while having none. This poses no specific requirements on the constructor of an object, therefore. On the other hand, the values we can extract from such context are few... and they do pose issues with current compilers, too... Well, let's fake it!

template <typename T, typename U>
constexpr std::size_t offsetof_impl(T const* t, U T::* a) {
    return (char const*)t - (char const*)&(t->*a) >= 0 ?
           (char const*)t - (char const*)&(t->*a)      :
           (char const*)&(t->*a) - (char const*)t;
}

#define offsetof(Type_, Attr_)                          \
    offsetof_impl((Type_ const*)nullptr, &Type_::Attr_)

The only issue I foresee is with virtual inheritance, because of its runtime placement of the base object. I would be glad to be presented with other defects, if there are.

Chaos answered 1/11, 2012 at 17:23 Comment(14)
see also the built-in offsetof macroPasha
@markd: Beware => If type is not a standard layout type or if the member is a static data member or a function member, then the behavior is undefined of using the offsetof macro as specified by the C standard is undefined. In other words, the offsetof macro of C works only with C-like types.Chaos
Why are you using T const* and char const* instead of just T* and char*?Bring
@ens: to make it explicit that the function does not modify its input (it does not through the macro, but it may be used without).Chaos
@MatthieuM. Just bitten by new compiler here :) I guess the intentioin of the standards people is to make the offsets of base classes within a class non-constant but if a field is in a base class, even if that base class in non-virtual it does not work. I can understand why virtual base classes would be all over the place but for simple bases ? I had an old template where you would put in as a base class the "node" for a list ( a intrusive node on the object ) And the generic list code only needed the offset to the node. Alas can't do that :|Spite
Wouldn't be ptrdiff_t more useful?Samella
@Keinstein: It's certainly better than int (which could be too short), however the offset is always positive, so might as well encode that fact in the return type (I switched to std::size_t).Chaos
It depends on the definition of offset. While arrays are usually count from zero to positive numbers, in memory management the offset usually just means the difference. See en.wikipedia.org/wiki/Offset_(computer_science) .Samella
@Keinstein: The offset of a data member is still always positive, regardless. You can't have a data member positioned before the value it belongs to.Chaos
If the offset is always positive, and if the lifeworkspace cod above is meant for something else than statistics. Shouldn't it then signal an error condition in case it gets negative? And not just take the absolute value? If some keen guy does some clever pointer arithmetics the absoulte value throws away important information.Samella
@Keinstein: If some keen guy uses the offsetof macro in ways it was not intended, then it'll blow up in their face indeed. I guess an assert would be better, but otherwise as Mr Babbage said: Garbage In, Garbage Out.Chaos
Links redirect to some bullshit malicious website.Drachm
@KulaGGin: Urk, thanks. I've removed them.Chaos
np. Also, it appears that it doesn't work. I wrote a simple test and it doesn't compile: godbolt.org/z/9dE655dd1 . It returns offset 0 for the Member2, because when I change the line to static_assert(offsetof(MyClass, Member2) == 0), it compiles, which demonstrates that it doesn't work. That's on MSVC x64. On Clang 17.0.1 x86-64 I get static assertion expression is not an integral constant expression. Well, I'll just continue using the macros from <cstddef>: en.cppreference.com/w/cpp/types/offsetofDrachm
C
4

No, not in general.

The offsetof macro exists for POD (plain old data) structs, and it may be extended slightly with C++0x to standard layout structs (or other similar slight extensions). So for those restricted cases, you have a solution.

C++ offers a lot of freedom to compiler writers. I don't know of any clause that would prevent some classes from having variable offsets to members of the class -- however, I'm not sure why a compiler would do that either. ;)

Now, one approach to keep your code standards compliant, yet still have offsets, would be to stick your data into a POD (or some C++0x extension) sub-struct, on which offsetof will work, then work on that sub-struct instead of on the entire class. Or you could surrender standards compliance. The offset of your struct within your class wouldn't be known, but the offset of the member within the struct would be.

An important question to ask is "why do I want this, and do I really have a good reason"?

Cacie answered 1/11, 2012 at 16:42 Comment(7)
It may not be a really good reason, but some database interactions require compile-time constant offsets into data structures in order to construct indexes. It's possible to hard-code these, but letting the compiler calculate them instead can avoid some errors.Handed
@austin That is a case foe plain old data: putting a non-standard layout class into a database that way will be a bad idea.Cacie
Members of virtual bases are typically at variable offsetsChecklist
"I don't know of any clause that would prevent some classes from having variable offsets to members of the class" You don't need a clause. It's not really possible, considering &Type::Member returns a constexpr offset. Also, how would you even generate assembly for that and make it compliant with inheritance demands.Drachm
Or you could surrender standards compliance. The offset of your struct within your class wouldn't be known. Can you show some example where offsetof from cstddef header doesn't work? As in, at runtime I'd get a different offset with ((void*)&Member - (void*)this) compared to offsetof(Type, Member)Drachm
@Drachm There are C++ implementations that are on top of scripting engines that use something akin to javascript "bag of properties" for objects. Something being constexpr doesn't mean it is an integer whose value can be determined at compile time, it just means it can be used at compile time; As a concrete example, the address of global data is constexpr, but at load time global data is put at runtime determined actual memory locations; its value is NOT compile time known, but &global is still usable at compile time.Cacie
@Drachm And sorry, I'm not qualified to know what every compiler that exists or will exist does in an arbitrary non-standard compliant case. In a few cases I happen to know, but I don't really seek this knowledge. If your goal is to have code that works on a few specific compiler (with fixed versions), feel free to use the "it works, so ship it" technique. Otherwise, I will recommend attempting to do something standards compliant unless standards compliance leads to a really large overhead. I have been bitten by doing something non-compliant and having compilers update and break it.Cacie
D
1

Is it possible to calculate the offset of a class member at compile time using C++ template meta-programming? The class is not POD, and can have virtual methods, primitive and object data member.

For your requirements the offsetof macro from cstddef header will do just fine.

It correctly returns offsets for class members of built-in and user-defined types and even if the class has virtual methods in it. Here's an example with tests of classes with virtual methods and user-defined members: https://godbolt.org/z/YY8Y6E378

// Type your code here, or load an example.
#include <cstddef>

class MyClass2 {
    public:
    virtual void Function() {

    }

    int Integer{};
};

class MyClass {
public:
    virtual void Function() {

    }
    
    MyClass2 MyClassMember{};
    int Member{};
    int Member2{};
    MyClass2 MyClassMember2{};
};

static_assert(offsetof(MyClass, MyClassMember) == 0x8);
static_assert(offsetof(MyClass, Member) == 0x18);
static_assert(offsetof(MyClass, Member2) == 0x1C);
static_assert(offsetof(MyClass, MyClassMember2) == 0x20);

int main() {
    return 0;
}

This was compiled for x64, so you get respective offsets.

You can edit them and see how the offsets change.

Drachm answered 23/11, 2023 at 11:33 Comment(0)
C
0

In the 1996 book "Inside the C++ Object Model", written by Stanley B. Lippman, one of the original C++ designer's, makes reference to Pointer-to-Member Functions in Chapter 4.4

the value returned from taking the address of a nonstatic data member is the byte value of the member's position in the class layout (plus 1). One can think of it as an incomplete value. It needs to be bound to the address of a class object before an actual instance of the member can be accessed.

While I vaguely recall that +1 from somewhere in some previous life, I've never seen or made use of this syntax before.

class t
{
public:
    int i;
    int j;
};
int (t::*pmf)() = &t::i;

At least according to the description, this seems to be a cool way to get "almost" the offset.

But it doesn't seem to work anymore, at least in GCC. I get an error:

Cannot initialize a variable of type 'int (t::*) with an rvalue of type "int t:: *'

Does anybody have any history behind what is going on here? Is something like this still possible?

Problem with the web -- obsolete books never die...

Churchman answered 23/11, 2016 at 3:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.