std :: bit_cast с использованием std :: array - PullRequest
14 голосов
/ 10 октября 2019

В своем недавнем выступлении «Пуннинг типа в современном C ++» Тимур Доумлер сказал , что std::bit_cast нельзя использовать для преобразования битов float в unsigned char[4], потому что CМассивы в стиле не могут быть возвращены из функции. Мы должны либо использовать std::memcpy, либо ждать до C ++ 23 (или позже), когда что-то вроде reinterpret_cast<unsigned char*>(&f)[i] станет хорошо определенным.

В C ++ 20 мы можем использовать std::array с std::bit_cast,

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

вместо массива в стиле C для получения байтов float?

Ответы [ 3 ]

15 голосов
/ 10 октября 2019

Да, это работает на всех основных компиляторах, и, насколько я могу судить по стандарту, он переносим и гарантированно работает.

Прежде всего, std::array<unsigned char, sizeof(float)> гарантированно будетагрегат (https://eel.is/c++draft/array#overview-2). Из этого следует, что он содержит ровно sizeof(float) число char s внутри (обычно как char[], хотя на самом деле стандарт не предписывает эту конкретную реализацию - но он делаетскажем, элементы должны быть смежными) и не могут иметь каких-либо дополнительных нестатических элементов.

Поэтому его можно копировать тривиально, а его размер совпадает с размером float.

Эти два свойствапозволяют вам bit_cast между ними.

5 голосов
/ 10 октября 2019

Принятый ответ неверен, поскольку не учитывает проблемы выравнивания и заполнения.

За [массив] / 1-3 :

Заголовок<array> определяет шаблон класса для хранения последовательностей фиксированного размера объектов. Массив является смежным контейнером. Экземпляр array<T, N> хранит N элементов типа T, так что size() == N является инвариантом.

Массив - это агрегат, который может быть инициализирован списком максимум с N элементамичьи типы могут быть преобразованы в T.

Массив удовлетворяет всем требованиям контейнера и обратимого контейнера ([container.requirements]), за исключением того, что созданный по умолчанию объект массива не является пустым, и этот своп делаетне имеют постоянной сложности. Массив отвечает некоторым требованиям контейнера последовательности. Описания предоставлены здесь только для операций над массивом, которые не описаны ни в одной из этих таблиц, и для операций, в которых есть дополнительная семантическая информация.

Стандарт фактически не требует, чтобы std::array имел ровно однуоткрытый элемент данных типа T[N], поэтому теоретически возможно, что sizeof(To) != sizeof(From) или is_­trivially_­copyable_­v<To>.

Я буду удивлен, если на практике это не сработает.

2 голосов
/ 10 октября 2019

Да.

В соответствии с документом , который описывает поведение std::bit_cast, и его предлагаемой реализацией , поскольку оба типа имеют одинаковый размер итривиально копируемые, приведение должно быть успешным.

Упрощенная реализация std::bit_cast должна выглядеть примерно так:

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

Так как float (4 байта) и массив unsigned charПри size_of(float) уважении всех этих утверждений, базовое std::memcpy будет выполнено. Поэтому каждый элемент в результирующем массиве будет одним последовательным байтом с плавающей точкой.

Чтобы доказать это поведение, я написал небольшой пример в Compiler Explorer, который вы можете попробовать здесь: https://godbolt.org/z/4G21zS.Число с плавающей запятой 5.0 правильно хранится в виде массива байтов (Ox40a00000), который соответствует шестнадцатеричному представлению этого числа с плавающей запятой в Big Endian .

...