Generic pamięć oparta na char [] i unikanie UB ściśle związanego z aliasingiem

Usiłuję zbudować szablon klasy, który pakuje kilka typów w odpowiednio dużą tablicę znaków i umożliwia dostęp do danych jako osobne poprawnie wpisane odwołania. Teraz, zgodnie ze standardem, może to prowadzić do naruszenia ścisłego aliasingu, a zatem niezdefiniowanego zachowania, ponieważ uzyskujemy dostęp dochar[] dane przez obiekt, który nie jest z nim zgodny. W szczególności standard określa:

Jeśli program próbuje uzyskać dostęp do przechowywanej wartości obiektu za pomocą wartości innej niż jeden z następujących typów, zachowanie jest niezdefiniowane:

dynamiczny typ obiektu,wersja dynamiczna typu obiektu kwalifikowana przez cv,typ podobny (jak zdefiniowano w 4.4) do typu dynamicznego obiektu,typ, który jest typem podpisanym lub niepodpisanym, odpowiadającym dynamicznemu typowi obiektu,typ, który jest typem podpisanym lub niepodpisanym odpowiadającym wersji dynamicznej typu obiektu kwalifikowanej przez cv,typ agregacji lub unii, który zawiera jeden z wyżej wymienionych typów wśród swoich elementów lub nie statycznych członków danych (w tym rekurencyjnie element lub niestatyczny element danych subaggregatu lub unii zawartej),typ będący (być może kwalifikowanym przez cv) typem klasy bazowej typu dynamicznego obiektu,a char lubunsigned char rodzaj.

Biorąc pod uwagę brzmienie wyróżnionego punktu, wymyśliłem następującealias_cast pomysł:

#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;
}

(Powyższy kod testu, zwłaszczaData klasa to po prostu głupia demonstracja tego pomysłu, więc proszę nie zwracać uwagi na to, jak powinienem używaćstd::pair lubstd::tuple. Thealias_cast szablon powinien być również rozszerzony, aby obsługiwał typy kwalifikowane cv i może być bezpiecznie użyty tylko wtedy, gdy spełnione są wymagania wyrównania, ale mam nadzieję, że ten fragment wystarczy, aby zademonstrować ten pomysł.)

Ta sztuczka wycisza ostrzeżenia g ++ (po skompilowaniu zg++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing), a kod działa, ale czy jest to naprawdę prawidłowy sposób nakazania kompilatorowi pominięcia optymalizacji opartej na aliasach ścisłych?

Jeśli nie jest poprawny, to w jaki sposób można by zaimplementować klasyczną klasę pamięci masowej opartą na tablicy char bez naruszania reguł aliasingu?

Edytuj: zastępującalias_cast z prostymreinterpret_cast lubię to:

T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }

generuje następujące ostrzeżenie podczas kompilacji za pomocą g ++:

aliastest-so-1.cpp: W instancji „T & Data :: first () [z T = int; U = short unsigned int]: aliastest-so-1.cpp: 28: 16:
wymagane stąd aliastest-so-1.cpp: 21: 58: ostrzeżenie: dereferencja typu wykrzyknika przerwie reguły ścisłego aliasingu [-Wstrict-aliasing]

questionAnswers(1)

yourAnswerToTheQuestion