Спецификатор 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в обоих местах.

Другие вопросы по тегам