Я пытаюсь создать шаблон класса, который собирает кучу типов в достаточно большом массиве char и позволяет получить доступ к данным в виде отдельных правильно напечатанных ссылок. Теперь, согласно стандарту, это может привести к нарушению строгого сглаживания и, следовательно, поведения undefined, поскольку мы обращаемся к данным char[]
через объект, который несовместим с ним. В частности, стандартные состояния:
Если программа пытается получить доступ к сохраненному значению объекта с помощью glvalue, отличного от одного из следующих типов, поведение undefined:
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, аналогичный (как определено в 4.4) для динамического типа объекта,
- тип, который является подписанным или неподписанным типом, соответствующим динамическому типу объекта,
- тип, который является подписанным или неподписанным типом, соответствующим квитанционной версии динамического типа объекта,
- тип агрегата или объединения, который включает один из вышеупомянутых типов среди его элементов или нестатических членов данных (включая рекурсивно элемент или нестатический элемент данных субагрегата или содержащегося объединения),
- тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
- a
char
илиunsigned char
.
Учитывая формулировку выделенной точки, я придумал следующую идею alias_cast
:
#include <iostream>
#include <type_traits>
template <typename T>
T alias_cast(void *p) {
typedef typename std::remove_reference<T>::type BaseType;
union UT {
BaseType t;
};
return reinterpret_cast<UT*>(p)->t;
}
template <typename T, typename U>
class Data {
union {
long align_;
char data_[sizeof(T) + sizeof(U)];
};
public:
Data(T t = T(), U u = U()) { first() = t; second() = u; }
T& first() { return alias_cast<T&>(data_); }
U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};
int main() {
Data<int, unsigned short> test;
test.first() = 0xdead;
test.second() = 0xbeef;
std::cout << test.first() << ", " << test.second() << "\n";
return 0;
}
(Вышеуказанный тестовый код, особенно класс Data
, является просто демонстрацией идеи, поэтому, пожалуйста, не указывайте, как я должен использовать std::pair
или std::tuple
. Шаблон alias_cast
также должен быть расширен для обработки квалифицированных типов, и его можно безопасно использовать только в том случае, если требования к выравниванию выполнены, но я надеюсь, что этого фрагмента достаточно, чтобы продемонстрировать идею.)
Этот трюк заглушает предупреждения g++ (при компиляции с помощью g++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing
), и код работает, но действительно ли это действительный способ сообщить компилятору пропустить оптимизацию на основе строкового сглаживания?
Если это недействительно, тогда как можно было бы реализовать такой универсальный класс хранения, основанный на массиве char, не нарушая правила псевдонимов?
Изменить:
заменив alias_cast
на простой reinterpret_cast
следующим образом:
T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }
выдает следующее предупреждение при компиляции с g++:
aliastest-so-1.cpp: при создании "T & Данные:: first() [с T = int; U = короткий unsigned int]: aliastest-so-1.cpp: 28: 16:
требуется отсюда aliastest-so-1.cpp: 21: 58: предупреждение: разыменование type-punned pointer будет нарушать правила строгого сглаживания [-Wstrict сглаживание]