c++ c++20 type-punning

c++ - std:: bit_cast con std:: array



c++20 type-punning (3)

La respuesta aceptada es incorrecta porque no tiene en cuenta los problemas de alineación y relleno.

Por [array]/1-3 :

El encabezado <array> define una plantilla de clase para almacenar secuencias de objetos de tamaño fijo. Una matriz es un contenedor contiguo. Una instancia de la array<T, N> almacena N elementos de tipo T , por lo que size() == N es invariante.

Una matriz es un agregado que se puede inicializar en una lista con hasta N elementos cuyos tipos son convertibles a T

Una matriz cumple con todos los requisitos de un contenedor y de un contenedor reversible ( [container.requirements] ), excepto que un objeto de matriz construido predeterminado no está vacío y ese intercambio no tiene una complejidad constante. Una matriz cumple con algunos de los requisitos de un contenedor de secuencia. Las descripciones se proporcionan aquí solo para operaciones en una matriz que no se describen en una de estas tablas y para operaciones donde hay información semántica adicional.

El estándar en realidad no requiere que std::array tenga exactamente un miembro de datos públicos de tipo T[N] , por lo que en teoría es posible que sizeof(To) != sizeof(From) o is_trivially_copyable_v<To> .

Sin embargo, me sorprendería si esto no funciona en la práctica.

En su reciente charla "Punking de tipo en C ++ moderno", Timur Doumler said que std::bit_cast no se puede usar para convertir un float en un unsigned char[4] porque las matrices de estilo C no se pueden devolver de una función. Deberíamos usar std::memcpy o esperar hasta C ++ 23 (o posterior) cuando algo como reinterpret_cast<unsigned char*>(&f)[i] esté bien definido.

En C ++ 20, ¿podemos usar un std::array con std::bit_cast ,

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

en lugar de una matriz de estilo C para obtener bytes de un float ?


Sí, esto funciona en todos los compiladores principales, y por lo que puedo ver al observar el estándar, es portátil y garantiza que funcione.

En primer lugar, se garantiza que std::array<unsigned char, sizeof(float)> es un agregado ( https://eel.is/c++draft/array#overview-2 ). De esto se deduce que contiene exactamente un número de sizeof(float) de caracteres en el interior (generalmente como un char[] , aunque afaics el estándar no exige esta implementación en particular, pero sí dice que los elementos deben ser contiguos) y no puede tener cualquier miembro no estático adicional.

Por lo tanto, es trivialmente copiable, y su tamaño coincide con el del float también.

Esas dos propiedades le permiten bit_cast entre ellas.


Si.

De acuerdo con el paper que describe el comportamiento de std::bit_cast , y su implementación propuesta en la medida en que ambos tipos tengan el mismo tamaño y se puedan copiar trivialmente, la conversión debe ser exitosa.

Una implementación simplificada de std::bit_cast debería ser algo como:

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

Dado que un float (4 bytes) y una matriz de caracteres unsigned char con size_of(float) respetan todas esas std::memcpy , se realizará el std::memcpy subyacente. Por lo tanto, cada elemento en la matriz resultante será un byte consecutivo del flotante.

Para probar este comportamiento, escribí un pequeño ejemplo en Compiler Explorer que puedes probar aquí: https://godbolt.org/z/4G21zS . El float 5.0 se almacena correctamente como una matriz de bytes ( Ox40a00000 ) que corresponde a la representación hexadecimal de ese número flotante en Big Endian .