The following code results in the compiler warning:
dereferencing type-punned pointer will break strict-aliasing rules
#include <endian.h>
template <typename T>
inline T HostToLittleEndian(T val) {
switch (sizeof(val)) {
case 1:
return val;
case 2: {
uint16_t r = htole16(*reinterpret_cast<uint16_t*>(&val));
return *reinterpret_cast<T*>(&r);
}
case 4: {
uint32_t r = htole32(*reinterpret_cast<uint32_t*>(&val));
return *reinterpret_cast<T*>(&r);
}
case 8: {
uint64_t r = htole64(*reinterpret_cast<uint64_t*>(&val));
return *reinterpret_cast<T*>(&r);
}
default:
static_assert(sizeof(val) <= 8, "Value is 64-bits or less.");
}
}
I was able to refactor using a union, which resolves the compiler warning, but now there's an extra copy of val. This other StackOverflow question makes me believe the copy is unavoidable, is this true? Or is there a more optimal solution I'm failing to realize?
template <typename T>
inline T HostToLittleEndian(T val) {
union {
T value;
uint64_t u64;
uint32_t u32;
uint16_t u16;
uint8_t u8;
} data;
data.value = val;
switch (sizeof(val)) {
case 1:
break;
case 2:
data.u16 = htole16(data.u16);
break;
case 4:
data.u32 = htole32(data.u32);
break;
case 8:
data.u64 = htole64(data.u64);
break;
default:
static_assert(sizeof(val) <= 8, "Value is 64-bits or less.");
}
return data.value;
}
Note, both integral and float types are valid T types.
When
Tis an integral type, you don't really need the casts, as implicit conversions should work just fine, eg:UPDATE:
To handle floating-point types, you should copy them into a local buffer, swap the bytes, and then copy back (see: swapping "endianness" of floats and doubles), eg:
This will also work for integral types too, but that is not strictly necessary.