Я не знаю, насколько это соответствует стандартам, но согласно Godbolt , следующий код компилируется чисто в clang, gcc и MSVC и генерирует правильный код (push 4, в основном, для m2) эффективным способом:
#include "stdio.h"
template <typename T, typename M> int member_offset (M m)
{
const void *pv = nullptr;
const T *pT = static_cast <const T *> (pv);
return static_cast <int> (reinterpret_cast <const char *> (&(pT->*m)) - reinterpret_cast <const char *> (pT));
}
class x
{
public:
int m1;
int m2;
};
int main (void)
{
int m1_offset = member_offset <x> (&x::m1);
int m2_offset = member_offset <x> (&x::m2);
printf ("m1_offset=%d, m2_offset=%d\n", m1_offset, m2_offset);
}
Вывод из Wandbox :
Start
m1_offset=0, m2_offset=4
0
Finish
Теперь вы можете просто использовать или сравнить member_offset's
, чтобы делать все, что вы хотите.
EDIT
Как указывалось выше Caleth и Deduplicator, это не работает с виртуальным наследованием. Смотрите мой последний комментарий к ответу Дедупликатора по причине. Кроме того, мне интересно, что при доступе к переменным экземпляра в базовом классе при использовании виртуального наследования возникают значительные накладные расходы. Я этого не понял.
Кроме того, следующий простой макрос проще в использовании и корректно работает с множественным наследованием с помощью clang (так много для всех этих причудливых шаблонов):
#define member_offset(classname, member) \
((int) ((char *) &((classname*) nullptr)->member - (char *) nullptr))
Вы можете проверить это с помощью gcc и clang в Wandbox :
#include "stdio.h"
#define member_offset(classname, member) \
((int) ((char *) &((classname *) nullptr)->member - (char *) nullptr))
struct a { int m1; };
struct b { int m2; };
struct c : a, b { int m3; };
int main (void)
{
int m1_offset = member_offset (c, m1);
int m2 = member_offset (c, m2);
int m3 = member_offset (c, m3);
printf ("m1_offset=%d, m2=%d, m3=%d\n", m1_offset, m2, m3);
}
Выход:
m1_offset=0, m2=4, m3=8
Но если вы используете этот макрос с классом, который использует виртуальное наследование, вы получите SEGFAULT (потому что компилятору нужно заглянуть внутрь объекта, чтобы найти смещение членов данных этого объекта, и там нет объекта - просто nullptr ).
Итак, ответ на вопрос ОП заключается в том, что вам нужен экземпляр для этого в общем случае. Возможно, есть специальный конструктор, который ничего не делает для создания одного из них с минимальными издержками.
ВТОРОЕ РЕДАКТИРОВАНИЕ
Я подумал еще об этом, и мне пришло в голову, что вместо того, чтобы сказать:
int mo = member_offset (c, m);
Вместо этого вы должны сказать:
constexpr int mo = member_offset (c, m);
Тогда компилятор должен предупредить вас, если класс c использует виртуальное наследование.
К сожалению, ни clang, ни gcc не скомпилируют это для какого-либо класса, виртуального наследования или нет. MSVC, с другой стороны, делает и генерирует ошибку компилятора, только если класс c использует виртуальное наследование.
Я не знаю, какой компилятор делает все правильно, поскольку стандарт идет, но поведение MSVC, очевидно, является наиболее разумным.