2

I have an array of floats (4 bytes per float) and I want to copy the data to an array of bytes (uint8_t) and treat it as bytes. Later I may want to copy the byte data back to a float array and treat it again as floats for various operations. Is this valid in c++? In other words is something like this where I treat the floats temporarily as bytes valid?

std::array<std::uint8_t, 40> b;
b.fill(0);
std::array<float,10> f;
f.fill(3.14);
std::memcpy(b.data(),f.data(),40);
std::array<float,10> f2;
f2.fill(0);
std::memcpy(f2.data(),b.data(),40);
for(std::size_t i=0;i<10;i++)
{
  std::cout<<f2[i]<<std::endl; //now i want to access the float data
} 
astrophobia
  • 839
  • 6
  • 13
  • 2
    In C++ that's actually the only valid way to do type-punning and not break strict aliasing, to copy to a byte array and back from the byte array. My only possible objection would be the reason behind this. Why do you need to do this? What is the *real* problem you need to solve? – Some programmer dude Feb 05 '19 at 08:06
  • 1
    Possible duplicate of [Does memcpy preserve data between different types?](https://stackoverflow.com/questions/42329820/does-memcpy-preserve-data-between-different-types) – P.W Feb 05 '19 at 08:08
  • You can `reinterpret_cast` `f.data()` to `unsigned char *` as well, there is no need to `memcpy` around, as `unsigned char` is [allowed](http://eel.is/c++draft/basic.lval#11.8) to alias. – geza Feb 05 '19 at 13:23

1 Answers1

7

Yeah, it's mostly fine1. I'm assuming uint8_t aliases a character type (which it does on modern platforms). Floats are trivially copyable, so moving them about like that is explicitly allowed:

[basic.types]

2 For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes ([intro.memory]) making up the object can be copied into an array of char, unsigned char, or std​::​byte ([cstddef.syn]). If the content of that array is copied back into the object, the object shall subsequently hold its original value.

The standard mandates you get back your floats if everything checks out. To be on the extra safe side one can add this check...

static_assert(std::is_same_v<unsigned char, std::uint8_t>);

... if you have too much code to alter. Otherwise, using std::byte is preferable to express you are dealing with raw bytes.


1 - See When is uint8_t ≠ unsigned char?, courtesy of @Ted Lyngmo.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Just out of curiosity regarding the `static_assert`: If `std::uint8_t` exists, can it be anything else than `unsigned char`? – Ted Lyngmo Feb 05 '19 at 08:47
  • @TedLyngmo - If `CHAR_BIT` is not 8, it obviously can't be an alias to a character. But an implementation may still offer support for unsigned modulo 256 arithmetic by some implementation specific type and/or emulation. There's not likely nowadays IMO, as I prefaced the post with. But one can't ignore the standard leaves that possibility open. – StoryTeller - Unslander Monica Feb 05 '19 at 08:52
  • Ok, that answers that. I thought that if `CHAR_BIT > 8` then `std::uint8_t` wouldn't be allowed to exist – Ted Lyngmo Feb 05 '19 at 09:02
  • 1
    @TedLyngmo - The fixed width types are optional. So it's up to the implemenation anyway. I suppose if `CHAR_BIT > 8` an implementation may not want to bother. But it can. I recall reading about platforms where floating point wasn't nativily supported, so the compiler translates that into library calls. Same thing can be done for any type really. – StoryTeller - Unslander Monica Feb 05 '19 at 09:04
  • [When is uint8_t ≠ unsigned char?](https://stackoverflow.com/questions/16138237/when-is-uint8-t-%E2%89%A0-unsigned-char) suggests `std::uint8_t` isn't allowed to exist when `CHAR_BIT > 8` - but it also says that if it exists it can be of a different type than `unsigned char` (even though they both are 8 bits) - so I see how the `static_assert` really is needed to be safe. – Ted Lyngmo Feb 05 '19 at 09:47
  • 1
    @TedLyngmo - Upon further reflection... I forgot the no padding requirement. So yea, `CHAR_BIT == 8` is a must. The thing I had in mind is `std::uint_least8_t`. – StoryTeller - Unslander Monica Feb 05 '19 at 10:54