https://github.com/lim-james/static-serial
[wip] zero-overhead binary serialization w/ C++26 Reflections
https://github.com/lim-james/static-serial
cpp26 reflections
Last synced: about 1 month ago
JSON representation
[wip] zero-overhead binary serialization w/ C++26 Reflections
- Host: GitHub
- URL: https://github.com/lim-james/static-serial
- Owner: lim-james
- Created: 2026-03-19T17:27:38.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-03-27T16:38:58.000Z (about 2 months ago)
- Last Synced: 2026-03-28T01:24:33.780Z (about 2 months ago)
- Topics: cpp26, reflections
- Language: C++
- Homepage:
- Size: 65.4 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Static Serial
`C++26` `Reflections` `GCC16`
Zero-overhead binary serialization w/ C++26 Reflection support
## Example
```cpp
#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(raw_bytes).object;
assert(bid == restored);
assert(raw_bytes.size() != sizeof(OrderBookLevel));
assert(raw_bytes.size() == 20); // excludes padding
```
## Getting Started
> Incomplete segment: to include supported compilers
1. Add `include/static_serial.hpp` into your project. (No support for modules yet)
2. Done.
## Public Interface
**Serialization Methods**
```cpp
template
[[nodiscard]] constexpr auto serialize(
const T& data,
Endian endianness = {}
) -> std::array>;
template
constexpr std::span serialize_into(
const T& data,
std::span destination,
Endian endianness = {}
);
[[nodiscard]] constexpr auto deserialize(
std::span data,
Endian endianness = {}
) -> DeserializeResult;
```
**Skip Member Annotation**
```cpp
inline constexpr auto skip = skipserialization{};
// e.g. [[=skip]] int* ignore_member;
```
**Check Serializability**
```cpp
template
[[nodiscard]] consteval bool is_serializable();
```
**Endian Specifiers**
```cpp
inline constexpr BigEndian big_endian{};
inline constexpr LittleEndian little_endian{};
inline constexpr NativeEndian native_endian{};
```
**Return Schema**
```cpp
template
[[nodiscard]] std::string schema();
```
**Deserialize Return Type**
```cpp
template
struct DeserializeResult {
T object;
std::span offset;
};
```
### Supported Types
> Intend to better define this in a future patch
A rule-of-thumb is serializable objects should have standard layout and no heap
owning members/pointers.
| 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 | |
## Zero-overhead Verification
Compiled with GCC 16 `-O2`
| Metric | `-O0` | `-O2` |
|-----------------|--------|-------|
| Function calls | 94 | 3 |
| Loop labels | 76 | 1 |
| Assembly lines | 2494 | 446 |
## Motivation
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](https://www.youtube.com/watch?v=ZX_z6wzEOG0) and his challenge to see how we can use reflections to solve problems.
## Resources
- [P2996R12 Paper](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2996r12.html#selecting-members)
- 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.
## C++26 features explored
1. `template for` expansion statements, complie-time iteration
2. `std::meta::nonstatic_data_members_of` extract all nonstatic members of given
type
3. `^^` reflection operator - compute reflection value
4. `[: refl :]` splicers - produce grammatical elements from reflections (copied
from P2996
5. `std::define_static_array` - takes a range and materialize a span for compile
time (consteval). Needed this for `template for`
6. `std::meta::size_of` why this over `sizeof`? Simply because sizeof
accomodates for padded bytes whilst meta::size_of is raw number of bytes
7. `std::meta::annotations_of` for option to omit a field from serialization
## Difficulties faced
In this section, I will be documenting problems I have faced whilst attempting
to learn.
1. Compiler & P-Paper interface mismatch. p-paper describes `member_of(info
r)` whilst the compiler takes in an additional parameter `access_context`
- Overcomed by referrencing header files directly
2. `members_of` returns 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
3. 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_serializable` check at the start of serializing
4. Static assert messages are a little vague
- Waiting for constexpr std::format [P3391](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3391r0.html)
5. `nonstatic_data_members_of` does not pick up private members
- use unchecked access context
## Known issues
1. std::tuple ~and std::pair~ support
2. ~private data members are not picked up~