I decided to put together my own class to do the job, starting with an easy, low-overhead way to move bytes in and out of arbitrary buffers. Along the way, it picked up useful bits and pieces, such as buffer structures and allocators that made the byte shuffling faster, often being able to do it with zero allocations and zero copies. Safety features came along to make sure that malicious packet data or mistakes in the code wouldn't result in segfaults or vulnerabilities.
It's become useful enough to me that I've packaged it up in its own standalone library on the chance that it might be useful to others. It has zero dependencies other than the standard library and has been designed for quick integration into any project within minutes, or seconds with a copy paste of the amalgamated header. It can be used in production code but it's also ideal for for those that want to quickly hack away at binary data with minimal fuss.
Consider copying Cereal, which solves this problem by requiring you to create a single templated function ( https://uscilab.github.io/cereal/ )
I'd like to read an even more thorough overview of how it works and all the gotchas before I'd consider using this 'in production' but the API looks very easy to use and very elegant.
EDIT: just hit the section on portability, seems like you would always have to use that API, yeah? I feel like when you are writing network code you simply have to make it portable from the get-go. I guess I'm always thinking about having it run on client machines.
You'd likely want to always use that API (or layer something on top of it) unless you're in control of both ends and know they were built with the same toolchain & settings. One area where I've skipped over it is by writing a basic code gen tool (albeit unfinished as most personal projects) that generates the serialisation functions at compile-time from a very basic DSL that describes the network structures (of a game protocol I don't control). If it detects that the current toolchain is going to generate a binary-compatible struct layout and there aren't any variable length fields in there (no strings, basically), it'll generate a memcpy (via using get/put on the stream) rather than per-field (de)serialisation. If it can guarantee alignment of the buffer, which is a tougher requirement to meet, it'll give you a view directly into the network buffer so you effectively have zero-overhead deserialisation. Very much a work in progress but there's scope for making things quite efficient with just a few basic building blocks.
Bjarne appears to prefer cout though, so it isn't universal.
On US layout colon is a single keypress but < is shift+.
This may explain the discrepancy.
—- from someone who read Bjarne at 16yo. All hail the Bjarne
Array programming languages smugly enter the chat
I tried adding std::string to the UserPacket (from the README)
and the compilation fails - https://onlinegdb.com/B_RJd5UwsBasically, if it seems like memcpying the structure might be a reasonable thing to do, it'll work. This is why types like std::array will work but std::vector and std::string won't. It can handle those types when inserted individually but not in aggregate since there's no reflection.
The compiler barf does tell the user why it was rejected but... average C++ errors, even with concepts. Not the greatest.
main.cpp:136:52: note: the expression ‘is_trivial_v [with T = UserPacket]’ evaluated to ‘false’ 136 | concept pod = std::is_standard_layout_v<T> && std::is_trivial_v<T>;
https://github.com/eliasdaler/MetaStuff
Another take on the same idea with even simpler interface:
https://github.com/apankrat/cpp-serializer
> What does that mean
Have you ever noticed that the (compile time) "rules" for interacting with templated functions are somewhat different from those of non-templated functions? I don't know if "functions as types" is entirely fair but there is definitely some weirdness.
https://en.cppreference.com/w/cpp/language/sfinae
It can generate efficient JS and C++ from a simple YAML file.
It’s far better than the other binary serialization protocols I’ve looked at / implemented. NFSv3 uses it, and it is compatible with a lot of the tricks you play, like in-place endian translation, branch avoidance, zero allocation use cases, etc:
https://www.rfc-editor.org/rfc/rfc1014
I don't use the amalgamated version, though (that only exists for this standalone version) and the library overall is significantly smaller than either of those and doesn't drag in nearly as many standard library headers.
But soon you’ll be bitten by the fact you don’t have a schema and so you’ll move to something like Protobuf or the more efficient FlatBuffers
since the underlying storage is std::array<char, ...>, it's alignment may be less that the required alignment of the requested type and that of the pointers being stored in the free list.
I've also built some tooling on top that makes use of those functions to do zero-copy deserialisation where viable, so it is possible in the right scenarios with a bit of work but it definitely isn't going to always fit.
I would have liked a different froggy reaction for each section but the project budget was zero. :^)