std :: bit_cast з std :: масивом


14

У своїй нещодавній бесіді «Тип покарання в сучасному C ++» Тимур Домлер сказав, що std::bit_castйого неможливо використати для введення біт floatв «a», 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);

замість масиву в стилі С, щоб отримати байти float?

Відповіді:


15

Так, це працює на всіх основних компіляторах, і наскільки я можу сказати, переглядаючи стандарт, він портативний і гарантовано працює.

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

Тому він є тривіально скопійованим, і його розмір також відповідає розміру float.

Ці два властивості дозволяють вам знаходитись bit_castміж ними.


3
Зверніть увагу, що struct X { unsigned char elems[5]; };відповідає правилу, яке ви цитуєте. Це, безумовно, може бути ініціалізовано список з до 4 елементами. Він також може бути ініціалізований у списку з 5 елементами. Я не думаю, що будь-який стандартний виконавець бібліотеки ненавидить людей достатньо, щоб насправді це зробити, але я думаю, що це технічно відповідає.
Баррі

Дякую! - Баррі, я не думаю, що це цілком правильно. Стандарт говорить: "може бути ініціалізовано список з до N елементів". Моє тлумачення полягає в тому, що "до" означає "не більше". Що означає, що ти не можеш цього робити elems[5]. І в цей момент я не бачу, як ти можеш закінчитись із сукупністю sizeof(array<char, sizeof(T)>) != sizeof(T)?
Тімур Домлер

Я вважаю, що мета цього правила ("сукупність, яку можна ініціалізувати у списку ...") - дозволити struct X { unsigned char c1, c2, c3, c4; };або struct X { unsigned char elems[4]; };- або, хоча символи повинні бути елементами цього сукупності, це дозволяє їм бути або прямими елементами сукупності. або елементи одного сукупності.
Тимур Домлер

2
@Timur "до" не означає "не більше". Точно так само, як P -> Qце означає, що це не означає нічого про випадок, коли!P
Баррі

1
Навіть якщо агрегат не містить нічого, крім масиву з точно 4-х елементів, немає гарантії, що arrayсам по собі не матиме прокладки. Втілення в нього може не мати прокладки (а будь-які реалізації, які це потрібно, слід вважати дисфункціональними), але немає жодної гарантії, що arrayсама по собі цього не буде.
Ніколь

6

Прийнята відповідь є невірною, оскільки вона не враховує проблеми вирівнювання та заміщення.

За [масив] / 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

Так.

Згідно з документом, який описує поведінку 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;
}

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

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


Ви впевнені, що std::arrayгарантовано не будете мати шматочки прокладки тощо?
LF

1
На жаль, сам факт того, що деякий код працює, не передбачає в ньому жодного UB. Наприклад, ми можемо записати auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)і отримати точно такий же вихід. Це щось доводить?
Evg

@LF відповідно до специфікації: std::arrayзадовольняє вимогам ContiguiosContainer (оскільки C ++ 17) .
Мануель Гіл

1
@ManuelGil: std::vectorтакож відповідає тим самим критеріям і очевидно не може бути використаний тут. Чи є щось вимагає, щоб std::arrayутримувати елементи всередині класу (у полі), що не дозволяє йому бути простим вказівником на внутрішній масив? (як у векторному, який також має розмір, який масив не вимагає мати у полі)
firda

@firda Сукупна вимога std::arrayфактично вимагає, щоб він зберігав елементи всередині, але я переживаю за проблеми з компонуванням.
LF
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.