std :: bit_cast con std :: array


14

Nel suo recente discorso "Digitare la punzonatura nel moderno C ++" Timur Doumler ha affermato che std::bit_castnon può essere utilizzato per eseguire il bit di bit floatin un unsigned char[4]perché gli array in stile C non possono essere restituiti da una funzione. Dovremmo usare std::memcpyo attendere fino a C ++ 23 (o successivo) quando qualcosa del genere reinterpret_cast<unsigned char*>(&f)[i]diventerà ben definito.

In C ++ 20, possiamo usare un std::arraycon std::bit_cast,

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

invece di un array in stile C per ottenere byte di un float?

Risposte:


15

Sì, questo funziona su tutti i principali compilatori e, per quanto ne so guardando lo standard, è portatile e garantito per funzionare.

Innanzitutto, std::array<unsigned char, sizeof(float)>è garantito che sia un aggregato ( https://eel.is/c++draft/array#overview-2 ). Da ciò consegue che contiene esattamente un sizeof(float)numero di chars all'interno (in genere come a char[], sebbene afaics lo standard non imponga questa particolare implementazione - ma dice che gli elementi devono essere contigui) e non può avere altri membri non statici.

È quindi banalmente copiabile e anche le sue dimensioni corrispondono a quelle di float.

Queste due proprietà ti permettono bit_casttra di loro.


3
Nota che struct X { unsigned char elems[5]; };soddisfa la regola che stai citando. Può certamente essere inizializzato in elenco con un massimo di 4 elementi. Può anche essere inizializzato in elenco con 5 elementi. Non credo che nessun implementatore di librerie standard odia le persone abbastanza da farlo, ma penso che sia tecnicamente conforme.
Barry,

Grazie! - Barry, non credo sia giusto. Lo standard dice: "può essere inizializzato con un massimo di N elementi". La mia interpretazione è che "fino a" implica "non più di". Ciò significa che non puoi farlo elems[5]. E a quel punto non riesco a vedere come potresti finire con un aggregato dove sizeof(array<char, sizeof(T)>) != sizeof(T)?
Timur Doumler,

Credo che lo scopo della regola ("un aggregato che può essere inizializzato in elenco ...") sia quello di consentire uno struct X { unsigned char c1, c2, c3, c4; };o struct X { unsigned char elems[4]; };- quindi mentre i caratteri devono essere gli elementi di quell'aggregato, ciò consente loro di essere sia elementi aggregati diretti o elementi di un singolo sub-aggregato.
Timur Doumler,

2
@Timur "fino a" non implica "non più di". Allo stesso modo in cui l'implicazione P -> Qnon implica nulla del caso in cui!P
Barry,

1
Anche se l'aggregato non contiene nient'altro che una matrice di esattamente 4 elementi, non vi è alcuna garanzia che esso arraystesso non avrà il riempimento. Le implementazioni potrebbero non avere imbottitura (e qualsiasi implementazione che dovrebbe essere considerata disfunzionale), ma non c'è alcuna garanzia che arraynon lo sia.
Nicol Bolas,

6

La risposta accettata non è corretta perché non considera i problemi di allineamento e riempimento.

Per [array] / 1-3 :

L'intestazione <array>definisce un modello di classe per la memorizzazione di sequenze di oggetti di dimensioni fisse. Un array è un contenitore contiguo. Un'istanza di elementi di tipo array<T, N>store , quindi è un invariante.NTsize() == N

Un array è un aggregato che può essere inizializzato in elenco con un massimo di N elementi i cui tipi sono convertibili T.

Una matrice soddisfa tutti i requisiti di un contenitore e di un contenitore reversibile ( [container.requirements]), tranne per il fatto che un oggetto matrice predefinito costruito non è vuoto e che lo scambio non ha complessità costante. Un array soddisfa alcuni dei requisiti di un contenitore di sequenze. Le descrizioni sono fornite qui solo per le operazioni sull'array che non sono descritte in una di queste tabelle e per le operazioni in cui vi sono ulteriori informazioni semantiche.

Lo standard in realtà non richiede std::arraydi avere esattamente un membro di dati pubblici di tipo T[N], quindi in teoria è possibile che sizeof(To) != sizeof(From)o is_­trivially_­copyable_­v<To>.

Sarò sorpreso se questo non funziona in pratica, però.


2

Sì.

Secondo il documento che descrive il comportamento std::bit_caste l' implementazione proposta nella misura in cui entrambi i tipi hanno le stesse dimensioni e sono banalmente copiabili, il cast dovrebbe avere successo.

Un'implementazione semplificata di std::bit_castdovrebbe essere qualcosa del tipo:

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

Poiché un galleggiante (4 byte) e una serie di unsigned charcon size_of(float)rispetto tutti coloro afferma il sottostante std::memcpysarà effettuata. Pertanto, ogni elemento nell'array risultante sarà un byte consecutivo del float.

Per dimostrare questo comportamento, ho scritto un piccolo esempio in Compiler Explorer che puoi provare qui: https://godbolt.org/z/4G21zS . Il float 5.0 è correttamente memorizzato come un array di byte ( Ox40a00000) che corrisponde alla rappresentazione esadecimale di quel numero float in Big Endian .


Sei sicuro std::arraydi non avere punte di imbottitura ecc.?
LF

1
Sfortunatamente, il semplice fatto che alcuni codici funzionino non implica alcun UB in esso. Ad esempio, possiamo scrivere auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)e ottenere esattamente lo stesso output. Dimostra qualcosa?
Evg

@LF secondo le specifiche: std::arraysoddisfa i requisiti di ContiguiosContainer (dal C ++ 17) .
Manuel Gil,

1
@ManuelGil: std::vectorsoddisfa anche gli stessi criteri e ovviamente non può essere utilizzato qui. C'è qualcosa che richiede che std::arraymantenga gli elementi all'interno della classe (in un campo), impedendogli di essere un semplice puntatore alla matrice interna? (come nel vettore, che ha anche una dimensione, che l'array non richiede di avere in un campo)
firda

@firda Il requisito aggregato di std::arrayrichiede effettivamente di memorizzare gli elementi all'interno, ma mi preoccupo dei problemi di layout.
LF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.