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

Имеет ли тип phantom такое же выравнивание, как и исходное?

Рассмотрим следующую структуру, содержащую некоторые значения среды:

struct environment_values {
  uint16_t humidity;
  uint16_t temperature;
  uint16_t charging;
};

Я хотел бы добавить дополнительную информацию к этим значениям с типом phantom * и сделать их типы различными в одно и то же время:

template <typename T, typename P>
struct Tagged {
    T value;
};

// Actual implementation will contain some more features
struct Celsius{};
struct Power{};
struct Percent{};

struct Environment {
  Tagged<uint16_t,Percent> humidity;
  Tagged<uint16_t,Celsius> temperature;
  Tagged<uint16_t,Power>   charging;
};

Является ли макет памяти Environment таким же, как environment_values? Поддерживается ли это также для смешанных макетов типов, например:

struct foo {
    uint16_t value1;
    uint8_t  value2;
    uint64_t value3;
}

struct Foo {
    Tagged<uint16_t, Foo>  Value1;
    Tagged<uint8_t , Bar>  Value2;
    Tagged<uint64_t, Quux> Value3;
}

Для всех типов, которые я пробовал до сих пор, были проведены следующие утверждения:

template <typename T, typename P = int>
constexpr void check() {
    static_assert(alignof(T) == alignof(Tagged<T,P>), "alignment differs");
    static_assert(sizeof(T)  == sizeof(Tagged<T,P>),  "size differs");
}

// check<uint16_t>(), check<uint32_t>(), check<char>() …

Поскольку размер тегов с тегами и непомеченных вариантов тоже один и тот же, я думаю, что ответ должен быть да, но я хотел бы иметь определенную уверенность.

* Я не знаю, как эти тегированные значения вызывают в С++. "Сильно типизированные typedefs"? Я взял имя из Haskell.

4b9b3361

Ответ 1

Стандарт упоминает в [basic.align]/1:

Типы объектов имеют требования к выравниванию (3.9.1, 3.9.2), которые ограничения на адреса, на которых может быть объект такого типа выделены. Выравнивание - это целочисленное значение, определенное реализациейпредставляющее количество байтов между последовательными адресами, на которых данный объект может быть выделен. Тип объекта налагает выравнивание требование для каждого объекта этого типа; более строгое выравнивание может быть запрашивается с использованием спецификатора выравнивания (7.6.2).

Кроме того, [basic.compound]/3, упоминает:

Представление значений типов указателей определяется реализацией. Указатели на совместимые с макетами типы должны иметь одинаковое значение требования представления и выравнивания (6.11). [Примечание: указатели на (6.11) не имеют специального представления, но их диапазон допустимых значений ограничен расширенным выравниванием Требование].

В результате есть гарантия того, что совместимые с макетами типы имеют одинаковое выравнивание.

struct { T m; } и T не совместимы с макетами.

Как указано здесь, для того, чтобы два элемента были совместимыми с макетами, они оба должны быть стандартными типами макета, и их нестатические элементы данных должны встречаться с теми же типами и в том же порядке.

struct { T m; } содержит только a T, но T является T, поэтому он не может содержать T в качестве своего первого нестатического элемента данных.

Ответ 2

В соответствии с буквой закона размер и выравнивание типов определяются реализацией, и стандарт дает вам немного, если какие-либо гарантии о том, что вернутся sizeof и alignof.

template <typename T, typename P>
struct Tagged {
    T value;
};

Теоретически компилятору разрешено добавлять дополнение к концу этой структуры, что, очевидно, изменит размер и, возможно, выравнивание. На практике, единственный раз, когда я мог предположить, что это происходит, если T был предоставлен какой-то специфический для компилятора "упакованный" атрибут, но Tagged не был (но даже тогда GCC, похоже, работает нормально).

В любом случае, я бы сказал, что было бы неплохо добавить некоторые статические утверждения, чтобы убедиться, что компилятор разумен - это именно то, что вы сделали:).

Ответ 3

Как упоминалось gsamaras, стандарт гарантирует одинаковое выравнивание для классов, совместимых с макетами.

К сожалению, struct { T m; } и T не совместимы с макетами.

В 12.2.21 в стандарте изложены требования к классу совместимости с макетами:

Два типа стандартной структуры (раздел 12) - это классы, совместимые с макетами, если их общая начальная последовательность содержит все члены и битовые поля обоих классов (6.9).

И определение общей исходной последовательности приведено в 12.2.20:

Общая начальная последовательность двух типов структуры стандартного макета (раздел 12) - это самая длинная последовательность нестатических элементов данных и бит-полей в порядке объявления, начиная с первого такого объекта в каждой из структур, что соответствующие объекты имеют совместимые с макетами типы, и ни один из них не является битовым полем, либо оба являются битовыми полями с одинаковой шириной. [Пример:
struct A { int a; char b; };
struct B { const int b1; volatile char b2; };
struct C { int c; unsigned : 0; char b; };
struct D { int d; char b : 4; };
struct E { unsigned int e; char b; };
Общая начальная последовательность A и B содержит все члены любого из классов. Общая начальная последовательность A и C и A и D содержит первый член в каждом случае. Общая начальная последовательность A и E пуста.
- конец примера]

Таким образом, мы можем сделать следующие важные наблюдения:

  • Совместимость макетов ограничена строго стандартными классами макета. (Или перечисления используют один и тот же базовый тип или тривиальный случай, когда T и T2 являются буквально одним и тем же типом. См. 6.9.11.) В общем случае T не является стандартным классом макета. На самом деле, T не является даже классом в вашем примере (это uint16_t, верьте или нет, это имеет значение в соответствии со стандартом.) *
  • Даже если T гарантированно является стандартным классом макета, struct { T m; } не имеет общей начальной последовательности с T. Последовательность struct { T m; } начинается с T, тогда как последовательность T начинается с любых T нестатических элементов данных. На самом деле это строго гарантировано не как T, поскольку класс не может содержать себя по значению.

Следовательно, гарантия не может быть сохранена буквой стандарта. Вы должны продолжать выполнять ионы static_assert, чтобы убедиться, что ваш компилятор ведет себя так, как вы ожидаете.

* См. большинство вопросов о пушках типа объединения.