std :: bit_cast dengan std :: array

14

Dalam ceramahnya yang baru-baru ini "Tipe punning in C ++ modern" Timur Doumler mengatakan bahwa std::bit_casttidak dapat digunakan untuk menggigit sebuah floatke dalam unsigned char[4]karena array gaya-C tidak dapat dikembalikan dari suatu fungsi. Kita harus menggunakan std::memcpyatau menunggu sampai C ++ 23 (atau lebih baru) ketika sesuatu seperti reinterpret_cast<unsigned char*>(&f)[i]akan menjadi terdefinisi dengan baik.

Di C ++ 20, bisakah kita menggunakan std::arraywith std::bit_cast,

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

bukannya array C-style untuk mendapatkan byte dari float?

Evg
sumber

Jawaban:

15

Ya, ini bekerja pada semua kompiler utama, dan sejauh yang saya tahu dari melihat standar, ini portabel dan dijamin berfungsi.

Pertama-tama, std::array<unsigned char, sizeof(float)>dijamin sebagai agregat ( https://eel.is/c++draft/array#overview-2 ). Dari sini berikut ini memegang persis sizeof(float)sejumlah chars di dalam (biasanya sebagaichar[] , meskipun afaics standar tidak mengamanatkan implementasi khusus ini - tetapi ia mengatakan elemen harus berdekatan) dan tidak dapat memiliki anggota non-statis tambahan.

Oleh karena itu dapat disalin secara sepele, dan ukurannya cocok dengan float juga.

Kedua properti itu memungkinkan Anda untuk berada di bit_castantara keduanya.

Timur Doumler
sumber
3
Perhatikan bahwa struct X { unsigned char elems[5]; };memenuhi aturan yang Anda kutip. Ini tentu saja dapat diinisialisasi daftar hingga 4 elemen. Ini juga dapat diinisialisasi daftar dengan 5 elemen. Saya tidak berpikir ada pelaksana perpustakaan standar yang cukup membenci orang untuk benar-benar melakukan ini, tapi saya pikir itu sesuai secara teknis.
Barry
Terima kasih! - Barry, kurasa itu tidak benar. Standar mengatakan: "dapat diinisialisasi daftar hingga elemen N". Interpretasi saya adalah bahwa "hingga" menyiratkan "tidak lebih dari". Yang berarti Anda tidak dapat melakukannya elems[5]. Dan pada saat itu saya tidak bisa melihat bagaimana Anda bisa berakhir dengan agregat di mana sizeof(array<char, sizeof(T)>) != sizeof(T)?
Timur Doumler
Saya percaya tujuan aturan ("suatu agregat yang dapat diinisialisasi-daftar ...") adalah untuk memungkinkan salah satu struct X { unsigned char c1, c2, c3, c4; };atau struct X { unsigned char elems[4]; };- jadi sementara karakter harus merupakan elemen dari agregat tersebut, ini memungkinkan mereka untuk menjadi elemen agregat langsung atau elemen sub-agregat tunggal.
Timur Doumler
2
@Timur "hingga" tidak menyiratkan "tidak lebih dari". Dengan cara yang sama bahwa implikasinya P -> Qtidak menyiratkan apa pun tentang kasus di mana!P
Barry
1
Sekalipun agregat tidak berisi apa pun selain array yang persis terdiri dari 4 elemen, tidak ada jaminan bahwa arrayitu sendiri tidak akan memiliki bantalan. Implementasi itu mungkin tidak memiliki padding (dan implementasi apa pun yang harus dianggap disfungsional), tetapi tidak ada jaminan bahwaarray itu sendiri tidak akan.
Nicol Bolas
6

Jawaban yang diterima salah karena gagal mempertimbangkan masalah pelurusan dan bantalan.

Per [array] / 1-3 :

Header <array>mendefinisikan templat kelas untuk menyimpan urutan objek ukuran tetap. Array adalah wadah yang berdekatan. Sebuah instance dari array<T, N>menyimpan Nelemen dari tipe T, sehinggasize() == N adalah invarian.

Array adalah agregat yang dapat diinisialisasi daftar hingga N elemen yang tipenya dapat dikonversiT .

Array memenuhi semua persyaratan wadah dan wadah yang dapat dibalik ( [container.requirements]), kecuali bahwa objek array bawaan yang dibangun tidak kosong dan swap itu tidak memiliki kompleksitas konstan. Array memenuhi beberapa persyaratan wadah urutan. Deskripsi diberikan di sini hanya untuk operasi pada array yang tidak dijelaskan dalam salah satu tabel ini dan untuk operasi di mana ada informasi semantik tambahan.

Standar tidak benar-benar harus std::arraymemiliki tepat satu anggota data publik tipe T[N], jadi secara teori dimungkinkan bahwa sizeof(To) != sizeof(From)atau is_­trivially_­copyable_­v<To>.

Saya akan terkejut jika ini tidak berhasil dalam prakteknya.

LF
sumber
2

Iya.

Menurut kertas yang menggambarkan perilaku std::bit_cast, dan implementasi yang diusulkan sejauh kedua jenis memiliki ukuran yang sama dan dapat ditiru, para pemeran harus berhasil.

Implementasi yang disederhanakan std::bit_castharus berupa:

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

Karena float (4 byte) dan array unsigned chardengan size_of(float)hormat semua menegaskan, yang mendasarinyastd::memcpy akan dilakukan. Oleh karena itu, setiap elemen dalam array yang dihasilkan akan menjadi satu byte berturut-turut dari float.

Untuk membuktikan perilaku ini, saya menulis contoh kecil di Compiler Explorer yang dapat Anda coba di sini: https://godbolt.org/z/4G21zS . Float 5.0 disimpan dengan benar sebagai array byte ( Ox40a00000) yang sesuai dengan representasi heksadesimal dari nomor float di Big Endian .

Manuel Gil
sumber
Apakah Anda yakin std::arraydijamin tidak memiliki bit padding, dll.?
LF
1
Sayangnya, fakta bahwa beberapa kode berfungsi tidak menyiratkan tidak ada UB di dalamnya. Sebagai contoh, kita dapat menulis auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)dan mendapatkan hasil yang persis sama. Apakah itu membuktikan sesuatu?
Evg
@ LF sesuai dengan spesifikasi: std::arraymemenuhi persyaratan ContiguiosContainer (sejak C ++ 17) .
Manuel Gil
1
@ManuelGil: std::vectorjuga memenuhi kriteria yang sama dan jelas tidak dapat digunakan di sini. Apakah ada sesuatu yang mengharuskan std::arraymemegang elemen di dalam kelas (dalam bidang), mencegahnya menjadi pointer sederhana ke array batin? (seperti dalam vektor, yang juga memiliki ukuran, array mana yang tidak perlu ada dalam bidang)
firda
@ Firda Persyaratan agregat std::arraysecara efektif membutuhkannya untuk menyimpan elemen di dalamnya, tapi saya khawatir tentang masalah tata letak.
LF