Given a trivially constructible struct with alignment > 1, how can I create that struct with data from a buffer without calling the default constructor?
enum class FoodType {
Fruit, Veggie, Meat;
};
struct Banana {
int64_t hp;
int64_t deliciousness;
FoodType type;
};
Specifically, I don't want to default construct Banana
because I don't want to default init type
. I also cant copy a reinterpret_cast (Banana b = *reinterpret_cast<Banana*>(buffer)
) because the alignment of Banana
isn't 1.
Banana Read(char const* buffer) {
return WHAT_DO_I_DO(buffer);
}
You can solve this in C++17 using std::memcpy
as follows:
template <typename T>
// remove std::is_implicit_lifetime_v prior to C++23
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T Read(char const* buffer) {
alignas(T) unsigned char bytes[sizeof(T)];
std::memcpy(bytes, buffer, sizeof(T));
return *std::launder(reinterpret_cast<T*>(bytes));
}
This works because std::memcpy
implicitly creates a T
object in bytes
, which can be accessed via reinterpret_cast
.
However, this solution isn't constexpr
because of std::memcpy
and reinterpret_cast
.
This can be solved with C++20's std::bit_cast
:
template <typename T>
requires std::is_trivially_copyable_v<T>
constexpr T Read(char const* buffer) {
char bytes[sizeof(T)];
std::copy_n(buffer, sizeof(T), bytes);
return std::bit_cast<T>(bytes);
}
// Example usage (assuming little-endian, 32-bit int)
constexpr char bytes[] = {1, 2, 3, 4};
static_assert(Read<int>(bytes) == 0x04030201);
Note that while the implementation looks relatively expensive, it actually gets optimized down to just (https://godbolt.org/z/s78dGo7dM):
T Read<int>(char const*):
mov eax, dword ptr [rdi]
ret
Another solution based on C++23's std::start_lifetime_as
:
template <typename T>
requires std::is_implicit_lifetime_v<T>
constexpr T Read(char const* buffer) {
// implicit call to copy constructor here because we return T by value
return std::start_lifetime_as<T>(buffer);
}
However, this solution is purely theoretical because no compiler implements std::start_lifetime_as
yet.