Подтвердить что ты не робот

Как вычислить смещение члена класса во время компиляции?

Учитывая определение класса в С++

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

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

Можно ли вычислить смещение члена класса во время компиляции с использованием метапрограмм шаблона С++? Класс не POD и может иметь виртуальные методы, примитивный элемент и объект данных.

4b9b3361

Ответ 1

Основанный на Matthieu M. отвечает, но короче и без макросов:

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

И он называется вот так:

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

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

Edit:

Джейсон Райс прав. Это не приведет к фактическому постоянному выражению в С++ 11. Это не представляется возможным, учитывая ограничения в http://en.cppreference.com/w/cpp/language/constant_expression - в частности, разница указателей и reinterpret_cast не может быть в постоянном выражении.

Ответ 2

Ну... в С++ 11 вы действительно можете вычислить такие смещения правильно с помощью регулярных средств С++ (т.е. без делегирования конкретному встроенному компилятору).

В действии на liveworkspace:

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

Однако это означает, что t является ссылкой на экземпляр constexpr здесь, который может быть неприменим ко всем классам. Он не запрещает t иметь метод virtual, хотя и даже конструктор, если он является конструктором constexpr.

Тем не менее, это довольно мешает. В неоцененных контекстах мы могли бы реально использовать std::declval<T>() для моделирования наличия объекта; пока нет. Следовательно, это не создает особых требований к конструктору объекта. С другой стороны, значения, которые мы можем извлечь из такого контекста, немного... и они тоже создают проблемы с текущими компиляторами... Ну, пусть это подделает!

В действии на 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_)

Единственная проблема, которую я предполагаю, - это наследование virtual, из-за его размещения во время выполнения базового объекта. Я был бы рад получить другие недостатки, если они есть.

Ответ 3

Нет, не в общем.

Макрос offsetof существует для структур POD (простые старые данные), и его можно немного расширить с помощью С++ 0x до стандартных компоновочных структур (или других подобных небольших расширений). Поэтому для этих ограниченных случаев у вас есть решение.

С++ предлагает большую свободу писателям компилятора. Я не знаю ни одного предложения, которое помешало бы некоторым классам иметь переменные смещения для членов класса, но я не уверен, почему это сделает компилятор.;)

Теперь один из подходов, чтобы поддерживать совместимость ваших стандартов кода, но все же иметь смещения, - это привязать ваши данные к подструктуре POD (или некоторой С++ 0x extension), на которой будет выполняться функция offsetof, а затем работать над эту подструктуру вместо всего класса. Или вы можете отказаться от соблюдения стандартов. Смещение вашей структуры внутри вашего класса не будет известно, но смещение члена внутри структуры будет.

Важный вопрос: "Почему я хочу этого, и у меня действительно есть веская причина"?

Ответ 4

В книге 1996 года "Внутри модели объектов С++", написанной Стэнли Б. Липпманом, одним из первоначальных разработчиков С++, ссылается на функции указателя на член в главе 4.4.

значение, возвращаемое из адреса нестатического элемента данных, является байтовым значением позиции элемента в макете класса (плюс 1). Можно думать об этом как о неполной ценности. Он должен быть привязан к адресу объекта класса до того, как будет доступен доступ к фактическому экземпляру элемента.

Пока я смутно вспоминаю, что +1 откуда-то в какой-то предыдущей жизни, я никогда раньше не видел или не использовал этот синтаксис.

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

По крайней мере, согласно описанию, это, кажется, классный способ получить "почти" смещение.

Но он больше не работает, по крайней мере, в GCC. Я получаю

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

Есть ли у кого-нибудь история за тем, что здесь происходит? Возможно ли что-то подобное?

Проблема с сетью - устаревшие книги никогда не умирают...