Dans son récent discours "Type punning en C++ moderne" Timur Doumler dit que std::bit_cast
Ne peut pas être utilisé pour convertir un float
en un bit unsigned char[4]
Car les tableaux de style C ne peuvent pas être renvoyés par une fonction. Nous devons soit utiliser std::memcpy
Soit attendre C++ 23 (ou plus tard) quand quelque chose comme reinterpret_cast<unsigned char*>(&f)[i]
deviendra bien défini.
En C++ 20, pouvons-nous utiliser un std::array
Avec std::bit_cast
,
float f = /* some value */;
auto bits = std::bit_cast<std::array<unsigned char, sizeof(float)>>(f);
au lieu d'un tableau de style C pour obtenir des octets d'un float
?
Oui, cela fonctionne sur tous les principaux compilateurs, et pour autant que je sache en regardant la norme, il est portable et garanti de fonctionner.
Tout d'abord, std::array<unsigned char, sizeof(float)>
est garanti d'être un agrégat ( https://eel.is/c++draft/array#overview-2 ). Il s'ensuit qu'il contient exactement un sizeof(float)
nombre de char
à l'intérieur (généralement comme un char[]
, bien que afaics la norme ne rend pas obligatoire cette implémentation particulière - mais elle dit que les éléments doivent être contigus) et ne peut pas avoir de membres supplémentaires non statiques.
Il est donc trivialement copiable, et sa taille correspond également à celle de float
.
Ces deux propriétés vous permettent de bit_cast
entre eux.
Par [tableau]/1- :
L'en-tête
<array>
Définit un modèle de classe pour stocker des séquences d'objets de taille fixe. Un tableau est un conteneur contigu. Une instance dearray<T, N>
StockeN
éléments de typeT
, de sorte quesize() == N
est un invariant.Un tableau est un agrégat qui peut être initialisé par liste avec jusqu'à
N
éléments dont les types sont convertibles enT
.Un tableau répond à toutes les exigences d'un conteneur et d'un conteneur réversible (
[container.requirements]
), Sauf qu'un objet tableau construit par défaut n'est pas vide et que l'échange n'a pas une complexité constante. Un tableau répond à certaines des exigences d'un conteneur de séquence. Les descriptions sont fournies ici uniquement pour les opérations sur un tableau qui ne sont pas décrites dans l'un de ces tableaux et pour les opérations où il existe des informations sémantiques supplémentaires.
La norme n'exige pas réellement que std::array
Ait exactement un membre de données publiques de type T[N]
, Donc en théorie il est possible que sizeof(To) != sizeof(From)
ou is_trivially_copyable_v<To>
.
Je serai surpris si cela ne fonctionne pas dans la pratique.
Oui.
Selon le papier qui décrit le comportement de std::bit_cast
, Et son mise en œuvre proposée dans la mesure où les deux types ont la même taille et sont copiables, la distribution devrait avoir du succès.
Une implémentation simplifiée de std::bit_cast
Devrait ressembler à ceci:
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;
}
Puisqu'un float (4 octets) et un tableau de unsigned char
Avec size_of(float)
respectent toutes ces assertions, le std::memcpy
Sous-jacent sera exécuté. Par conséquent, chaque élément du tableau résultant sera un octet consécutif du flottant.
Afin de prouver ce comportement, j'ai écrit un petit exemple dans l'explorateur du compilateur que vous pouvez essayer ici: https://godbolt.org/z/4G21zS . Le flottant 5.0 est correctement stocké sous forme d'un tableau d'octets (Ox40a00000
) Qui correspond à la représentation hexadécimale de ce nombre flottant dans Big Endian .