简体   繁体   中英

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

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.

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_cast can be in a constant expression.

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

In action at liveworkspace :

template <typename T, typename U>
constexpr int 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!

In action at liveworkspace :

template <typename T, typename U>
constexpr 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.

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"?

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 a

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM