std :: bit_cast z std :: array

14

W swoim ostatnim przemówieniu „Pisanie czcionek we współczesnym C ++” Timur Doumler powiedział, że std::bit_castnie można go użyć do wrzucenia a floatdo, unsigned char[4]ponieważ tablice w stylu C nie mogą zostać zwrócone z funkcji. Powinniśmy albo użyć, std::memcpyalbo poczekać, aż C ++ 23 (lub nowszy), kiedy coś takiego reinterpret_cast<unsigned char*>(&f)[i]zostanie dobrze zdefiniowane.

W C ++ 20, możemy użyć std::arrayz std::bit_cast,

float f = /* some value */;
auto bits = std::bit_cast<std::array<unsigned char, sizeof(float)>>(f);

zamiast tablicy w stylu C, aby uzyskać bajty float?

Evg
źródło

Odpowiedzi:

15

Tak, działa to na wszystkich głównych kompilatorach i, o ile mogę stwierdzić po spojrzeniu na standard, jest przenośny i gwarantuje działanie.

Przede wszystkim std::array<unsigned char, sizeof(float)>gwarantuje się, że będzie agregatem ( https://eel.is/c++draft/array#overview-2 ). Wynika z tego, że zawiera dokładnie pewną sizeof(float)liczbę chars (zazwyczaj jako a char[], chociaż standard afaics nie nakazuje tej konkretnej implementacji - ale mówi, że elementy muszą być ciągłe) i nie może mieć żadnych dodatkowych elementów niestatycznych.

Dlatego jest trywialnie kopiowalny, a jego rozmiar odpowiada również rozmiarowi float.

Te dwie właściwości pozwalają bit_castmiędzy nimi.

Timur Doumler
źródło
3
Zauważ, że struct X { unsigned char elems[5]; };spełnia regułę, którą przytaczasz. Z pewnością można go zainicjować listą z maksymalnie 4 elementami. Można go również zainicjować listą z 5 elementami. Nie sądzę, żeby jakikolwiek standardowy implementator biblioteki nienawidził ludzi wystarczająco, by to zrobić, ale myślę, że jest to technicznie zgodne.
Barry
Dzięki! - Barry, nie sądzę, żeby to było w porządku. Standard mówi: „można zainicjalizować listę maksymalnie N elementami”. Moja interpretacja jest taka, że ​​„maksymalnie” oznacza „nie więcej niż”. Co oznacza, że ​​nie możesz tego zrobić elems[5]. I w tym momencie nie widzę, jak możesz skończyć z agregatem gdzie sizeof(array<char, sizeof(T)>) != sizeof(T)?
Timur Doumler,
Uważam, że celem reguły („agregacja, którą można zainicjować listą ...”) jest zezwolenie na jedno struct X { unsigned char c1, c2, c3, c4; };lub drugie struct X { unsigned char elems[4]; };- chociaż znaki muszą być elementami tego agregatu, to pozwala im być bezpośrednimi elementami agregującymi lub elementy pojedynczego agregatu.
Timur Doumler,
2
@Timur „do” nie oznacza „nie więcej niż”. W ten sam sposób, w jaki implikacja P -> Qnie implikuje niczego w sprawie, w której!P
Barry
1
Nawet jeśli agregat zawiera tylko tablicę dokładnie 4 elementów, nie ma gwarancji, że arraysam nie będzie miał wypełnienia. Implementacje mogą nie mieć wypełnienia (a wszelkie implementacje, które to robią, należy uznać za dysfunkcyjne), ale nie ma gwarancji, że arraysamo nie będzie.
Nicol Bolas,
6

Przyjęta odpowiedź jest nieprawidłowa, ponieważ nie uwzględnia problemów z wyrównaniem i dopełnianiem.

Na [tablicę] / 1-3 :

Nagłówek <array>definiuje szablon klasy do przechowywania sekwencji obiektów o stałym rozmiarze. Tablica jest ciągłym kontenerem. Instancja array<T, N>przechowuje Nelementy typu T, więc size() == Njest niezmienna.

Tablica jest agregatem, który można zainicjować listą do maksymalnie N elementów, na które typy są konwertowalne T.

Tablica spełnia wszystkie wymagania kontenera i kontenera odwracalnego ( [container.requirements]), z tym wyjątkiem, że domyślnie skonstruowany obiekt tablicy nie jest pusty i że zamiana nie ma stałej złożoności. Tablica spełnia niektóre wymagania kontenera sekwencji. Podano tutaj opisy tylko dla operacji na tablicy, które nie są opisane w jednej z tych tabel, oraz dla operacji, w których są dodatkowe informacje semantyczne.

Norma tak naprawdę nie wymaga std::arrayposiadania dokładnie jednego publicznego elementu danych typu T[N], więc teoretycznie możliwe jest, że sizeof(To) != sizeof(From)lub is_­trivially_­copyable_­v<To>.

Będę jednak zaskoczony, jeśli to nie zadziała w praktyce.

LF
źródło
2

Tak.

Zgodnie z artykułem opisującym zachowanie std::bit_casti jego proponowaną implementacją, o ile oba typy mają ten sam rozmiar i są w trywialny sposób kopiowalne, rzutowanie powinno zakończyć się powodzeniem.

Uproszczona implementacja std::bit_castpowinna wyglądać następująco:

template <class Dest, class Source>
inline Dest bit_cast(Source const &source) {
    static_assert(sizeof(Dest) == sizeof(Source));
    static_assert(std::is_trivially_copyable<Dest>::value);
    static_assert(std::is_trivially_copyable<Source>::value);

    Dest dest;
    std::memcpy(&dest, &source, sizeof(dest));
    return dest;
}

Ponieważ pływak (4 bajty) oraz tablicę unsigned charz size_of(float)spełniają wszystkich tych twierdzi bazowego std::memcpyzostaną przeprowadzone. Dlatego każdy element w wynikowej tablicy będzie jednym kolejnym bajtem liczby zmiennoprzecinkowej.

Aby udowodnić to zachowanie, napisałem mały przykład w Eksploratorze kompilatorów, który możesz wypróbować tutaj: https://godbolt.org/z/4G21zS . Liczba zmiennoprzecinkowa 5.0 jest poprawnie przechowywana jako tablica bajtów ( Ox40a00000), która odpowiada szesnastkowej reprezentacji tej liczby zmiennoprzecinkowej w Big Endian .

Manuel Gil
źródło
Czy masz pewność, że std::arraynie będziesz mieć bitów wypełniających itp.?
LF,
1
Niestety sam fakt, że jakiś kod działa, nie oznacza, że ​​nie ma w nim UB. Na przykład możemy pisać auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)i uzyskiwać dokładnie takie same dane wyjściowe. Czy to coś dowodzi?
Evg,
@LF zgodnie ze specyfikacją: std::arrayspełnia wymagania ContiguiosContainer (od C ++ 17) .
Manuel Gil,
1
@ManuelGil: std::vectorspełnia również te same kryteria i oczywiście nie można go tutaj zastosować. Czy jest coś, co wymaga std::arraytrzymania elementów wewnątrz klasy (w polu), co uniemożliwia, że ​​jest prostym wskaźnikiem do wewnętrznej tablicy? (jak w wektorze, który ma również rozmiar, którego tablica nie musi mieć w polu)
firda 10.10.19
@firda Łączne wymaganie std::arrayefektywnego wymaga przechowywania elementów w środku, ale martwię się problemami z układem.
LF