C++26 Reflections GCC16
Zero-overhead binary serialization w/ C++26 Reflection support
#include "static_serial.hpp"
auto bid = OrderBookLevel{
.price = 17250, // int64 - 8B
.quantity = 500, // uint64 - 8B
.order_count = 3 // uint32 - 4B
};
auto raw_bytes = stse::serialize(bid);
auto restored = stse::deserialize<OrderBookLevel>(raw_bytes).object;
assert(bid == restored);
assert(raw_bytes.size() != sizeof(OrderBookLevel));
assert(raw_bytes.size() == 20); // excludes padding
assert(stse::is_serializable_v<OrderBookLevel>);
assert(stse::serial_size_v<OrderBookLevel> != sizeof(OrderBookLevel));Incomplete segment: to include supported compilers
- Add
include/static_serial.hppinto your project. (No support for modules yet) - Done.
Serialization Methods
template<typename T, EndianType Endian = NativeEndian>
[[nodiscard]] constexpr auto serialize(
const T& data,
Endian endianness = {}
) -> std::array<std::byte, raw_size<T>>;
template<typename T, EndianType Endian = NativeEndian>
constexpr auto serialize_advance(
const T& data,
std::span<std::byte> destination,
Endian endianness = {}
) -> std::span<std::byte>;
template<typename T, EndianType Endian = NativeEndian>
[[nodiscard]] constexpr auto deserialize(
std::span<const std::byte> data,
Endian endianness = {}
) -> DeserializeResult<T>;
template<typename T, EndianType Endian = NativeEndian>
constexpr auto deserialize_advance(
T& parsed,
std::span<const std::byte> data,
Endian endianness = {}
) -> std::span<const std::byte>;Skip Member Annotation
inline constexpr auto skip = skipserialization{};
// e.g. [[=stse::skip]] int* ignore_member;Properties
template<typename T>
inline constexpr bool is_serializable_v;
template<typename T>
inline constexpr std::size_t serial_size_v;Endian Specifiers
inline constexpr BigEndian big_endian{};
inline constexpr LittleEndian little_endian{};
inline constexpr NativeEndian native_endian{};Return Schema
template<typename T>
[[nodiscard]] std::string schema();Deserialize Return Type
template<typename T>
struct DeserializeResult {
T object;
std::span<const std::byte> remaining;
};A rule-of-thumb is serializable objects should have no heap owning members/pointers, and should not inherit from a polymorphic class
- Decided to mark polymorphic classes as not serializable because of the existence of vptr
| Serializable | Non-Serializable |
|---|---|
| Scalar types | Pointers (& std::nullptr_t) |
| std::array with trivially copyable types | std::vector (any dynamically sized container) |
| std::pair with trivially copyable types | std::tuple |
| Aggregated structs | std::string |
| Nested structs |
Compiled with GCC 16 -O2
| Metric | -O0 |
-O2 |
|---|---|---|
| Function calls | 94 | 3 |
| Loop labels | 76 | 1 |
| Assembly lines | 2494 | 446 |
I have been hearing a lot about C++26's reflections and wanted to check it out. But I was primarily inspired by Barry Revzin's Practical Reflection at CppCon25 and his challenge to see how we can use reflections to solve problems.
- P2996R12 Paper
- GCC 16's header files
As of writing this project, I couldn't find any available resources but that's fine with me because I want to attempt a new form of learning.
template forexpansion statements, complie-time iterationstd::meta::nonstatic_data_members_ofextract all nonstatic members of given type^^reflection operator - compute reflection value[: refl :]splicers - produce grammatical elements from reflections (copied from P2996std::define_static_array- takes a range and materialize a span for compile time (consteval). Needed this fortemplate forstd::meta::size_ofwhy this oversizeof? Simply because sizeof accomodates for padded bytes whilst meta::size_of is raw number of bytesstd::meta::annotations_offor option to omit a field from serializationstd::meta::bases_offetch parent classes to recursively get member
In this section, I will be documenting problems I have faced whilst attempting to learn.
- Compiler & P-Paper interface mismatch. p-paper describes
member_of(info r)whilst the compiler takes in an additional parameteraccess_context- Overcomed by referrencing header files directly
members_ofreturns a vector which is heap allocated with new. Doesn't work directly at compile time esp template for.- Use define_static_array which takes in a range and extracts compile time information
- Using a recursive serializer leads to some really ugly error messages when an
invalid type is deeply nested in a type.
- Fixed it by introducing
is_serializablecheck at the start of serializing
- Fixed it by introducing
- Static assert messages are a little vague
- Waiting for constexpr std::format P3391
nonstatic_data_members_ofdoes not pick up private members- use unchecked access context
- Long build times
- use
-ftime-reportto breakdown build times. Reduce number of function overloads to cut build times by usingif constexpr
- use
- fetching data members of std::vector yields nothing
constexpr auto members = std::meta::nonstatic_data_members_of(
^^std::vector<int>,
std::meta::access_context::unchecked()
);
static_assert(members.size() == 0); // will pass- use `bases_of` to recursively fetch parent members (std::vector likely had its main mmeber in a subclass)
All resolved