std :: bit_cast com std :: array

14

Em sua recente palestra “Type punping in C ++ moderno”, Timur Doumler disse que std::bit_castnão pode ser usado para converter um bit floatem um, unsigned char[4]porque matrizes no estilo C não podem ser retornadas de uma função. Devemos usar std::memcpyou aguardar até C ++ 23 (ou mais recente) quando algo assim reinterpret_cast<unsigned char*>(&f)[i]se tornar bem definido.

Em C ++ 20, podemos usar um std::arraycom std::bit_cast,

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

em vez de uma matriz de estilo C para obter bytes de um float?

Evg
fonte

Respostas:

15

Sim, isso funciona em todos os principais compiladores e, até onde posso ver, olhando para o padrão, ele é portátil e com garantia de funcionamento.

Antes de tudo, std::array<unsigned char, sizeof(float)>é garantido que seja um agregado ( https://eel.is/c++draft/array#overview-2 ). A partir disso, ele contém exatamente um sizeof(float)número de chars dentro (normalmente como um char[], embora afaics o padrão não exija essa implementação específica - mas diz que os elementos devem ser contíguos) e não pode ter nenhum membro não estático adicional.

É, portanto, trivialmente copiável, e seu tamanho corresponde floatao mesmo.

Essas duas propriedades permitem que você bit_castentre elas.

Timur Doumler
fonte
3
Observe que struct X { unsigned char elems[5]; };satisfaz a regra que você está citando. Certamente pode ser inicializado em lista com até 4 elementos. Ele pode também ser com 5 elementos inicializado-list. Eu não acho que nenhum implementador de biblioteca padrão odeie pessoas o suficiente para fazer isso, mas acho que é tecnicamente compatível.
Barry
Obrigado! - Barry, não acho isso certo. O padrão diz: "pode ​​ser inicializado em lista com até N elementos". Minha interpretação é que "até" implica "não mais que". O que significa que você não pode fazer elems[5]. E, nesse ponto, não vejo como você pode acabar agregando onde sizeof(array<char, sizeof(T)>) != sizeof(T)?
Timur Doumler 11/11/19
Acredito que o objetivo da regra ("um agregado que pode ser inicializado em lista ...") seja permitir um struct X { unsigned char c1, c2, c3, c4; };ou outro struct X { unsigned char elems[4]; };- embora os caracteres precisem ser os elementos desse agregado, isso permite que sejam elementos agregados diretos ou elementos de um único subagregado.
Timur Doumler 11/11/19
2
O @Timur "até" não implica "não mais que". Da mesma forma que a implicação P -> Qnão implica nada sobre o caso em que!P
Barry
11
Mesmo que o agregado contenha apenas uma matriz de exatamente 4 elementos, não há garantia de que arrayele próprio não terá preenchimento. As implementações dele podem não ter preenchimento (e quaisquer implementações que o façam devem ser consideradas disfuncionais), mas não há garantia de que arrayele não tenha.
Nicol Bolas
6

A resposta aceita está incorreta porque não considera os problemas de alinhamento e preenchimento.

Por [matriz] / 1-3 :

O cabeçalho <array>define um modelo de classe para armazenar seqüências de objetos de tamanho fixo. Uma matriz é um contêiner contíguo. Uma instância de array<T, N>armazena Nelementos do tipo T, para quesize() == N é invariável.

Uma matriz é um agregado que pode ser inicializado em lista com até N elementos cujos tipos são conversíveis emT .

Uma matriz atende a todos os requisitos de um contêiner e de um contêiner reversível ( [container.requirements]), exceto que um objeto de matriz construído padrão não está vazio e que a troca não tem complexidade constante. Uma matriz atende a alguns dos requisitos de um contêiner de sequência. As descrições são fornecidas aqui apenas para operações na matriz que não são descritas em uma dessas tabelas e para operações nas quais há informações semânticas adicionais.

Na verdade, o padrão não requer std::arrayexatamente um membro de dados públicos do tipoT[N] ; portanto, em teoria, é possível que sizeof(To) != sizeof(From)ouis_­trivially_­copyable_­v<To> .

Ficarei surpreso se isso não funcionar na prática.

LF
fonte
2

Sim.

De acordo com o artigo que descreve o comportamento de std::bit_cast, e sua implementação proposta , na medida em que ambos os tipos têm o mesmo tamanho e são trivialmente copiáveis, o elenco deve ser bem-sucedido.

Uma implementação simplificada de std::bit_castdeve 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;
}

Como um float (4 bytes) e uma matriz unsigned charcom size_of(float)respeito a todas essas afirmações, o subjacente std::memcpyserá executado. Portanto, cada elemento na matriz resultante será um byte consecutivo do float.

Para provar esse comportamento, escrevi um pequeno exemplo no Compiler Explorer que você pode tentar aqui: https://godbolt.org/z/4G21zS . O flutuador 5.0 é armazenado corretamente como uma matriz de bytes ( Ox40a00000) que corresponde à representação hexadecimal desse número flutuante no Big Endian .

Manuel Gil
fonte
Tem certeza de que std::arraynão há bits de preenchimento, etc.?
LF
11
Infelizmente, o simples fato de algum código funcionar não implica em UB. Por exemplo, podemos escrever auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)e obter exatamente a mesma saída. Isso prova alguma coisa?
Evg
@LF de acordo com a especificação: std::arrayatende aos requisitos do ContiguiosContainer (desde C ++ 17) .
Manuel Gil
11
@ManuelGil: std::vectortambém atende aos mesmos critérios e obviamente não pode ser usado aqui. Existe algo exigindo que std::arraymantenha os elementos dentro da classe (em um campo), impedindo que ele seja um ponteiro simples para a matriz interna? (como no vector, o que também tem um tamanho, o qual matriz não necessita de ter em um campo)
FIRDA
@firda O requisito agregado de std::arrayefetivamente exige que ele armazene os elementos internos, mas estou preocupado com problemas de layout.
LF