Спецификатор alignas: по типу / по данным элемента
Следующие два случая эквивалентны во всех ситуациях?
1) alignas
по типу
template <typename T, size_t N, size_t Alignment>
struct alignas(Alignment) A
{
T data[N];
};
2) alignas
на массиве членов
template <typename T, size_t N, size_t Alignment>
struct A
{
alignas(Alignment) T data[N];
};
1 ответ
С точки зрения языкового юриста, они не эквивалентны. Расположение класса определяется реализацией, и гарантий мало. Одна из гарантий заключается в том, что если это тип стандартного макета, то обе версии вашего класса являются типами стандартного макета, что означает, что у них нет заполнения в начале ( [ class.mem.general]/27). В зависимости от указанного вами выравнивания в конце может потребоваться отступ, чтобы гарантировать, что его размер кратен этому выравниванию. Но в стандарте нет никакой гарантии, что реализация использует наименьшее необходимое количество дополнений. Например, если требование выравнивания равно 4 иN
равно 3, а вы запросили выравнивание 8, тогда любой класс может иметь 4, 12 или 20... байт заполнения в конце. На практике реализация будет использовать наименьшее необходимое количество отступов, и две ваши версии будут иметь один и тот же макет.
Если это не тип стандартной компоновки, реализации разрешено вставлять дополнение в начало , хотя маловероятно, что реализация действительно будет это делать. Количество дополнений, которые вставляет реализация, может различаться в двух версиях вашего файла .
Практические соображения обычно вынуждают два типа структур, которые имеют общую начальную последовательность , иметь одинаковое смещение для соответствующих элементов данных, чтобы эффективно реализовать [class.mem.general]/26 .Рассмотрим, например:
struct T1 {
int t11;
int t12;
};
struct T2 {
int t21;
int t22;
};
union U {
T1 t1;
T2 t2;
};
int foo(const U& u) {
return u.t1.t12;
}
Стандарт требует, чтобы, если активный член на самом деле вместоt1
, то этот код ведет себя так, как будто возвращаетu.t2.t22
. Чтобы реализовать это, компилятор гарантирует, что смещение внутри совпадает со смещением внутри, и просто обращается кint
который расположен по этому смещению от адресаu
.
Но если я говорю как языковой юрист, то должен сказать, что стандарт этого не гарантирует. Реализация C++ могла бы, предположительно, датьT1
иT2
различные макеты, хранить где-то дополнительную информацию, которая позволяет отслеживать активного членаU
объект, и когда он оцениваетu.t1.t12
, проверьте, является ли активный участникt2
, и если это так, то используйте смещениеt22
вместо того, чтобыt12
.
На практике все известные реализации C++ дают общую начальную последовательность двух структур стандартной компоновки, равных смещениям для соответствующих членов. Однако ваша стандартная раскладка используется только тогда, когдаT
имеет стандартный макет, и даже если ваши элементы имеют стандартный макет, общая начальная последовательность двух разных версий не обязательно будет включать этот элемент, поскольку в случае, когда спецификатор выравнивания применяется к самому себе, это не является ясно, что стандарт считает требованием выравниванияdata
член есть, и это означает, что [class.mem.general]/23.2 может не удовлетворяться.
При всем этом я был бы удивлен, если бы какая-либо реализация по-разному размещала две ваши версии (при идентичных аргументах шаблона). Практически говоря, вы можете рассчитывать на то, что они будут иметь одинаковую раскладку, но не должны этого делать , потому что я не вижу веских причин не просто писать то, что вы имеете в виду. Вы заботитесь о выравниванииA
, или выравнивание его члена? Какой бы из них вас ни беспокоил, напишите это. Или, если вас интересуют оба варианта, вы можете поместитьalignas
в обоих местах.