Sei que esse é um assunto bastante comum, mas, por mais fácil que seja o UB típico, não encontrei essa variante até agora.
Então, estou tentando introduzir formalmente objetos Pixel, evitando uma cópia real dos dados.
Isso é válido?
struct Pixel {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
};
static_assert(std::is_trivial_v<Pixel>);
Pixel* promote(std::byte* data, std::size_t count)
{
Pixel * const result = reinterpret_cast<Pixel*>(data);
while (count-- > 0) {
new (data) Pixel{
std::to_integer<uint8_t>(data[0]),
std::to_integer<uint8_t>(data[1]),
std::to_integer<uint8_t>(data[2]),
std::to_integer<uint8_t>(data[3])
};
data += sizeof(Pixel);
}
return result; // throw in a std::launder? I believe it is not mandatory here.
}
Padrão de uso esperado, altamente simplificado:
std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data
Mais especificamente:
- Esse código tem um comportamento bem definido?
- Se sim, torna seguro usar o ponteiro retornado?
- Em caso afirmativo, para que outros
Pixeltipos ele pode ser estendido? (relaxando a restrição is_trivial? pixel com apenas 3 componentes?).
Tanto o clang quanto o gcc otimizam todo o loop para o nada, que é o que eu quero. Agora, eu gostaria de saber se isso viola algumas regras do C ++ ou não.
Godbolt link, se você quiser brincar com ele.
(nota: apesar de não ter marcado o c ++ 17 std::byte, porque a pergunta continua sendo usada char)
pixels[some_index]ou *(pixels + something)? Isso seria UB.
pixels(P) não é um ponteiro para o objeto de matriz, mas um ponteiro para um único Pixel. Isso significa que você só pode acessar pixels[0]legalmente.
Pixels contíguos colocados como novos ainda não são uma matriz dePixels.