web-dev-qa-db-fra.com

std :: bit_cast avec std :: array

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?

14
Evg

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.

15
Timur Doumler

La réponse acceptée est incorrecte car elle ne prend pas en compte les problèmes d'alignement et de remplissage.

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 de array<T, N> Stocke N éléments de type T, de sorte que size() == 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 en T.

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.

6
L. F.

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 .

2
Manuel Gil