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
Pixel
tipos 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.
Pixel
s contíguos colocados como novos ainda não são uma matriz dePixel
s.