Info
-
Did you know that C++20
[[no_unique_address]]
can be used to implement lazy/fast/memory efficient views?
Example
template<class T, fixed_string Str, auto Offset = 0>
struct field {
constexpr const auto& get() const { return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(this) + Offset); }
constexpr operator const T&() const { return get(); }
};
enum class side : std::uint8_t { buy, sell };
struct trade {
[[no_unique_address]] field<::std::int32_t, "price"> price;
[[no_unique_address]] field<::std::uint32_t, "size", sizeof(std::int32_t)> quantity;
[[no_unique_address]] field<::side, "side", sizeof(std::int32_t) + sizeof(std::uint32_t)> side;
};
static_assert(1 == sizeof(trade));
auto parse(const void* msg) {
const auto& trade = *reinterpret_cast<const ::trade*>(msg);
assert(42 == trade.price);
assert(100 == trade.quantity);
assert(side::sell == trade.side);
}
int main() {
struct [[gnu::packed]] {
::std::int32_t price{42};
::std::uint32_t quantity{100};
::side side{side::sell};
} trade{};
parse(std::addressof(trade));
}
Puzzle
- Can you implement
buffer_cast
and extendfield
to support dynamic arrays?
template<class T, fixed_string Name, auto Offset = 0>
struct field; // TODO
template<class T> const T& buffer_cast(const void* msg); // TODO
enum class side : std::uint8_t { buy, sell };
struct trade final {
[[no_unique_address]] field<::std::int32_t, "price"> price;
[[no_unique_address]] field<::std::uint32_t, "size", sizeof(std::int32_t)> quantity;
[[no_unique_address]] field<::side, "side", sizeof(std::int32_t) + sizeof(std::uint32_t)> side;
};
static_assert(1 == sizeof(trade));
struct order final {
[[no_unique_address]] field<::std::int32_t, "price"> price;
[[no_unique_address]] field<::std::uint32_t, "size", sizeof(std::int32_t)> quantity;
[[no_unique_address]] field<::side, "side", sizeof(std::int32_t) + sizeof(std::uint32_t)> side;
[[no_unique_address]] field<::std::uint32_t, "len", sizeof(std::int32_t) + sizeof(std::uint32_t) + sizeof(::side)> len;
[[no_unique_address]] field<double[], "prices", sizeof(std::int32_t) + sizeof(std::uint32_t) + sizeof(::side) + sizeof(::std::uint32_t)> prices;
};
static_assert(1 == sizeof(order));
int main() {
using namespace boost::ut;
"buffer cast"_test = [] {
struct [[gnu::packed]] {
::std::int32_t price{1};
::std::uint32_t quantity{2};
::side side{side::buy};
} trade{};
const auto& msg = buffer_cast<::trade>(std::addressof(trade));
expect(1 == msg.price);
expect(2 == msg.quantity);
expect(side::buy == msg.side);
};
"buffer cast with dynamic array at the end"_test = [] {
struct [[gnu::packed]] {
::std::int32_t price{42};
::std::uint32_t quantity{100};
::side side{side::sell};
::std::uint32_t len{4};
std::array<double, 4> prices{.4, .3, .2, .1};
} order{};
const auto& msg = buffer_cast<::order>(std::addressof(order));
expect(42 == msg.price);
expect(100 == msg.quantity);
expect(side::sell == msg.side);
expect(4 == msg.len);
expect(.4 == msg.prices[0]);
expect(.3 == msg.prices[1]);
expect(.2 == msg.prices[2]);
expect(.1 == msg.prices[3]);
};
}
Solutions
template<class T, fixed_string Name, auto Offset = 0>
struct field {
[[nodiscard]] constexpr operator const T&() const noexcept {
return *std::launder(reinterpret_cast<const T*>(reinterpret_cast<const char*>(this) + Offset));
}
};
template<class T> [[nodiscard]] constexpr const T& buffer_cast(const void* msg) noexcept {
return *(::new (const_cast<void*>(msg)) const T);
}
template<class T, fixed_string Name, auto Offset = 0>
struct field {
constexpr const auto& get() const { return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(this) + Offset); }
constexpr operator const T&() const { return get(); }
};
template<class T> const T& buffer_cast(const void* msg) {
return *reinterpret_cast<const T*>(msg);
}
template<class T, fixed_string Name, auto Offset = 0>
struct field {
[[nodiscard]] constexpr const T& get() const noexcept {
// I'm pretty sure this is UB... since the struct is packed so the
// values are not aligned... :(
const auto addr = reinterpret_cast<const std::byte*>(this) + Offset;
return *std::launder(reinterpret_cast<const T*>(addr));
}
constexpr operator const T&() const noexcept { return get(); }
};
template <class T, fixed_string Name, auto Offset = 0>
struct field {
[[nodiscard]] constexpr const auto& get() const {
return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(this) +
Offset);
}
[[nodiscard]] constexpr operator const T&() const { return get(); }
};
template <class T>
[[nodiscard]] const T& buffer_cast(const void* msg) {
return *reinterpret_cast<const T*>(msg);
}