{"id":13424583,"url":"https://github.com/eyalz800/zpp_bits","last_synced_at":"2026-01-23T19:07:37.489Z","repository":{"id":38738447,"uuid":"439133047","full_name":"eyalz800/zpp_bits","owner":"eyalz800","description":"A lightweight C++20 serialization and RPC library","archived":false,"fork":false,"pushed_at":"2025-09-03T06:16:27.000Z","size":530,"stargazers_count":873,"open_issues_count":12,"forks_count":68,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-09-03T08:21:22.722Z","etag":null,"topics":["cpp","cpp20","header-only","rpc","serialization"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eyalz800.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-12-16T21:35:00.000Z","updated_at":"2025-09-03T06:16:30.000Z","dependencies_parsed_at":"2023-01-23T00:46:07.110Z","dependency_job_id":"2d1186a6-550f-40e5-ae98-ad9d86a281bf","html_url":"https://github.com/eyalz800/zpp_bits","commit_stats":{"total_commits":203,"total_committers":4,"mean_commits":50.75,"dds":"0.014778325123152691","last_synced_commit":"80d70a6692990969155b5bbb261fd8cbca077d95"},"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/eyalz800/zpp_bits","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eyalz800%2Fzpp_bits","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eyalz800%2Fzpp_bits/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eyalz800%2Fzpp_bits/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eyalz800%2Fzpp_bits/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eyalz800","download_url":"https://codeload.github.com/eyalz800/zpp_bits/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eyalz800%2Fzpp_bits/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28698373,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T17:25:48.045Z","status":"ssl_error","status_checked_at":"2026-01-23T17:25:47.153Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp20","header-only","rpc","serialization"],"created_at":"2024-07-31T00:00:56.632Z","updated_at":"2026-01-23T19:07:37.470Z","avatar_url":"https://github.com/eyalz800.png","language":"C++","funding_links":[],"categories":["Serialization","Serialization Library","RPC","Network"],"sub_categories":[],"readme":"zpp::bits\n=========\n\n[![.github/workflows/actions.yml](https://github.com/eyalz800/zpp_bits/actions/workflows/actions.yml/badge.svg)](https://github.com/eyalz800/zpp_bits/actions/workflows/actions.yml)\n[![Build Status](https://dev.azure.com/eyalz800/zpp_bits/_apis/build/status/eyalz800.zpp_bits?branchName=main)](https://dev.azure.com/eyalz800/zpp_bits/_build/latest?definitionId=9\u0026branchName=main)\n\nA modern C++20 binary serialization and RPC library, with just one header file.\n\nThis library is a successor to [zpp::serializer](https://github.com/eyalz800/serializer).\nThe library tries to be simpler for use, but has more or less similar API to its predecessor.\n\nContents\n--------\n* [Motivation](#motivation)\n* [Introduction](#introduction)\n* [Error Handling](#error-handling)\n* [Error Codes](#error-codes)\n* [Serializing Non-Aggregates](#serializing-non-aggregates)\n* [Serializing Private Classes](#serializing-private-classes)\n* [Explicit Serialization](#explicit-serialization)\n* [Archive Creation](#archive-creation)\n* [Constexpr Serialization](#constexpr-serialization)\n* [Position Control](#position-control)\n* [Standard Library Types Serialization](#standard-library-types-serialization)\n* [Serialization as Bytes](#serialization-as-bytes)\n* [Variant Types and Version Control](#variant-types-and-version-control)\n* [Literal Operators](#literal-operators)\n* [Apply to Functions](#apply-to-functions)\n* [Remote Procedure Call (RPC)](#remote-procedure-call-rpc)\n* [Byte Order Customization](#byte-order-customization)\n* [Deserializing View Of Const Bytes](#deserializing-views-of-const-bytes)\n* [Pointers as Optionals](#pointers-as-optionals)\n* [Reflection](#pointers-as-optionals)\n* [Additional Archive Controls](#additional-archive-controls)\n* [Variable Length Integers](#variable-length-integers)\n* [Protobuf](#protobuf)\n* [Advanced Controls](#advanced-controls)\n* [Benchmark](#benchmark)\n\nMotivation\n----------\n* Serialize any object from and to binary form as seamless as possible.\n* Provide lightweight remote procedure call (RPC) capabilities\n* Be the fastest possible - see the [benchmark](#benchmark)\n\n### The Difference From zpp::serializer\n* It is simpler\n* Performance improvements\n* Almost everything is `constexpr`\n* More flexible with serialization of the size of variable length types, opt-out from serializing size.\n* Opt-in for [zpp::throwing](https://github.com/eyalz800/zpp_throwing) if header is found.\n* More friendly towards freestanding (no exception runtime support).\n* Breaks compatibility with anything lower than C++20 (which is why the original library is left intact).\n* Better naming for utility classes and namespaces, for instance `zpp::bits` is more easily typed than `zpp::serializer`.\n* Modernized polymorphism, based on variant and flexible serialization ids, compared to `zpp::serializer` global\npolymorphic types with fixed 8 bytes of sha1 serialization id.\n* Lightweight RPC capabilities\n\nIntroduction\n------------\nFor many types, enabling serialization is transparent and requires no additional lines of code.\nPre-C++26, these types are required to be of aggregate type, with non array members.\nPost C++26, the feature \"(P1061) Structured bindings can introduce a pack\", lifts this requirement.\nHere is an example of a `person` class with name and age:\n```cpp\nstruct person\n{\n    std::string name;\n    int age{};\n};\n```\n\nExample how to serialize the person into and from a vector of bytes:\n```cpp\n// The `data_in_out` utility function creates a vector of bytes, the input and output archives\n// and returns them so we can decompose them easily in one line using structured binding like so:\nauto [data, in, out] = zpp::bits::data_in_out();\n\n// Serialize a few people:\nout(person{\"Person1\", 25}, person{\"Person2\", 35});\n\n// Define our people.\nperson p1, p2;\n\n// We can now deserialize them either one by one `in(p1)` `in(p2)`, or together, here\n// we chose to do it together in one line:\nin(p1, p2);\n```\n\nThis example almost works, we are being warned that we are discarding the return value.\nFor error checking keep reading.\n\nError Handling\n--------------\nWe need to check for errors, the library offers multiple ways to do so - a return value\nbased, exception based, or [zpp::throwing](https://github.com/eyalz800/zpp_throwing) based.\n\n### Using return values\nThe return value based way for being most explicit, or if you just prefer return values:\n```cpp\nauto [data, in, out] = zpp::bits::data_in_out();\n\nauto result = out(person{\"Person1\", 25}, person{\"Person2\", 35});\nif (failure(result)) {\n    // `result` is implicitly convertible to `std::errc`.\n    // handle the error or return/throw exception.\n}\n\nperson p1, p2;\n\nresult = in(p1, p2);\nif (failure(result)) {\n    // `result` is implicitly convertible to `std::errc`.\n    // handle the error or return/throw exception.\n}\n```\n\n### Using exceptions\nThe exceptions based way using `.or_throw()` (read this as \"succeed or throw\" - hence `or_throw()`):\n```cpp\nint main()\n{\n    try {\n        auto [data, in, out] = zpp::bits::data_in_out();\n\n        // Check error using `or_throw()` which throws an exception.\n        out(person{\"Person1\", 25}, person{\"Person2\", 35}).or_throw();\n\n        person p1, p2;\n\n        // Check error using `or_throw()` which throws an exception.\n        in(p1, p2).or_throw();\n\n        return 0;\n    } catch (const std::exception \u0026 error) {\n        std::cout \u003c\u003c \"Failed with error: \" \u003c\u003c error.what() \u003c\u003c '\\n';\n        return 1;\n    } catch (...) {\n        std::cout \u003c\u003c \"Unknown error\\n\";\n        return 1;\n    });\n}\n```\n\n### Using zpp::throwing\nAnother option is [zpp::throwing](https://github.com/eyalz800/zpp_throwing) where error checking turns into two simple `co_await`s,\nto understand how to check for error we provide a full main function:\n```cpp\nint main()\n{\n    return zpp::try_catch([]() -\u003e zpp::throwing\u003cint\u003e {\n        auto [data, in, out] = zpp::bits::data_in_out();\n\n        // Check error using `co_await`, which suspends the coroutine.\n        co_await out(person{\"Person1\", 25}, person{\"Person2\", 35});\n\n        person p1, p2;\n\n        // Check error using `co_await`, which suspends the coroutine.\n        co_await in(p1, p2);\n\n        co_return 0;\n    }, [](zpp::error error) {\n        std::cout \u003c\u003c \"Failed with error: \" \u003c\u003c error.message() \u003c\u003c '\\n';\n        return 1;\n    }, [](/* catch all */) {\n        std::cout \u003c\u003c \"Unknown error\\n\";\n        return 1;\n    });\n}\n```\n\nError Codes\n-----------\nAll of the above methods, use the following error codes internally and can be checked using the comparison\noperator from return value based, or by examining the internal error code of `std::system_error` or `zpp::throwing` depending\nwhich one you used:\n1. `std::errc::result_out_of_range` - attempting to write or read from a too short buffer.\n2. `std::errc::no_buffer_space` - growing buffer would grow beyond the allocation limits or overflow.\n3. `std::errc::value_too_large` - varint (variable length integer) encoding is beyond the representation limits.\n4. `std::errc::message_size` - message size is beyond the user defined allocation limits.\n5. `std::errc::not_supported` - attempt to call an RPC that is not listed as supported.\n6. `std::errc::bad_message` - attempt to read a variant of unrecognized type.\n7. `std::errc::invalid_argument` - attempting to serialize null pointer or a value-less variant.\n8. `std::errc::protocol_error` - attempt to deserialize an invalid protocol message.\n\nSerializing Non-Aggregates\n--------------------------\nPre-C++26, the library provides a way to serialize non-aggregates, requiring the user to provide\nthe number of members in the class.\nFor most non-aggregate types (or aggregate types with array members),\nenabling serialization is a one liner. Here is an example of a non-aggregate\n`person` class:\n```cpp\nstruct person\n{\n    // Add this line to your class with the number of members:\n    using serialize = zpp::bits::members\u003c2\u003e; // Two members\n\n    person(auto \u0026\u0026 ...){/*...*/} // Make non-aggregate.\n\n    std::string name;\n    int age{};\n};\n```\nMost of the time types we serialize can work with structured binding, and this library takes advantage\nof that, but you need to provide the number of members in your class for this to work using the method above.\n\nThis also works with argument dependent lookup, allowing to not modify the source class:\n```cpp\nnamespace my_namespace\n{\nstruct person\n{\n    person(auto \u0026\u0026 ...){/*...*/} // Make non-aggregate.\n\n    std::string name;\n    int age{};\n};\n\n// Add this line somewhere before the actual serialization happens.\nauto serialize(const person \u0026 person) -\u003e zpp::bits::members\u003c2\u003e;\n} // namespace my_namespace\n```\n\nPost C++26, the above works without a need to provide the number of members:\n```cpp\nnamespace my_namespace\n{\nstruct person\n{\n    person(auto \u0026\u0026 ...){/*...*/} // Make non-aggregate.\n\n    std::string name;\n    int age{};\n};\n\n} // namespace my_namespace\n```\n\n### Deprecated behavior\nIn some compilers, *SFINAE* works with `requires expression` under `if constexpr` and `unevaluated lambda expression`. It means\nthat even with non aggregate types the number of members can be detected automatically in cases where all members are in the same struct.\nTo opt-in, define `ZPP_BITS_AUTODETECT_MEMBERS_MODE=1`.\n```cpp\n// Members are detected automatically, no additional change needed.\nstruct person\n{\n    person(auto \u0026\u0026 ...){/*...*/} // Make non-aggregate.\n\n    std::string name;\n    int age{};\n};\n```\nThis works with `clang 13`, however the portability of this is not clear, since in `gcc` it does not work (it is a hard error) and it explicitly states\nin the standard that there is intent not to allow *SFINAE* in similar cases, so it is turned off by default.\nUpdate: this is not supported anymore with recent versions of `clang`, and as such `ZPP_BITS_AUTODETECT_MEMBERS_MODE=1` is deprecated.\n\nSerializing Private Classes\n---------------------------\nIf your data members or default constructor are private, you need to become friend with `zpp::bits::access`\nlike so:\n```cpp\nstruct private_person\n{\n    // Add this line to your class.\n    friend zpp::bits::access;\n    using serialize = zpp::bits::members\u003c2\u003e;\n\nprivate:\n    std::string name;\n    int age{};\n};\n```\n\nPost C++26, the above example is simplified to the below without a need to specify the number of members:\n```cpp\nstruct private_person\n{\n    friend zpp::bits::access;\n\nprivate:\n    std::string name;\n    int age{};\n};\n```\n\nExplicit Serialization\n----------------------\nTo enable save \u0026 load of any object using explicit serialization, which works\nregardless of structured binding compatibility, add the following lines to your class:\n```cpp\n    constexpr static auto serialize(auto \u0026 archive, auto \u0026 self)\n    {\n        return archive(self.object_1, self.object_2, ...);\n    }\n```\nNote that `object_1, object_2, ...` are the non-static data members of your class.\n\nHere is the example of a person class again with explicit serialization function:\n```cpp\nstruct person\n{\n    constexpr static auto serialize(auto \u0026 archive, auto \u0026 self)\n    {\n        return archive(self.name, self.age);\n    }\n\n    std::string name;\n    int age{};\n};\n```\n\nOr with argument dependent lookup:\n```cpp\nnamespace my_namespace\n{\nstruct person\n{\n    std::string name;\n    int age{};\n};\n\nconstexpr auto serialize(auto \u0026 archive, person \u0026 person)\n{\n    return archive(person.name, person.age);\n}\n\nconstexpr auto serialize(auto \u0026 archive, const person \u0026 person)\n{\n    return archive(person.name, person.age);\n}\n} // namespace my_namespace\n```\n\nArchive Creation\n----------------\nCreating input and output archives together and separately from data:\n```cpp\n// Create both a vector of bytes, input and output archives.\nauto [data, in, out] = zpp::bits::data_in_out();\n\n// Create just the input and output archives, and bind them to the\n// existing vector of bytes.\nstd::vector\u003cstd::byte\u003e data;\nauto [in, out] = zpp::bits::in_out(data);\n\n// Create all of them separately\nstd::vector\u003cstd::byte\u003e data;\nzpp::bits::in in(data);\nzpp::bits::out out(data);\n\n// When you need just data and in/out\nauto [data, in] = zpp::bits::data_in();\nauto [data, out] = zpp::bits::data_out();\n```\n\nArchives can be created from either one of the byte types:\n```cpp\n// Either one of these work with the below.\nstd::vector\u003cstd::byte\u003e data;\nstd::vector\u003cchar\u003e data;\nstd::vector\u003cunsigned char\u003e data;\nstd::string data;\n\n// Automatically works with either `std::byte`, `char`, `unsigned char`.\nzpp::bits::in in(data);\nzpp::bits::out out(data);\n```\n\nYou can also use fixed size data objects such as array, `std::array` and view types such as `std::span`\nsimilar to the above. You just need to make sure there is enough size since they are non resizable:\n```cpp\n// Either one of these work with the below.\nstd::byte data[0x1000];\nchar data[0x1000];\nunsigned char data[0x1000];\nstd::array\u003cstd::byte, 0x1000\u003e data;\nstd::array\u003cchar, 0x1000\u003e data;\nstd::array\u003cunsigned char, 0x1000\u003e data;\nstd::span\u003cstd::byte\u003e data = /*...*/;\nstd::span\u003cchar\u003e data = /*...*/;\nstd::span\u003cunsigned char\u003e data = /*...*/;\n\n// Automatically works with either `std::byte`, `char`, `unsigned char`.\nzpp::bits::in in(data);\nzpp::bits::out out(data);\n```\n\nWhen using a vector or string, it automatically grows to the right size, however, with the above\nthe data is limited to the boundaries of the arrays or spans.\n\nWhen creating the archive in any of the ways above, it is possible to pass a variadic\nnumber of parameters that control the archive behavior, such as for byte order, default size types,\nspecifying append behavior and so on. This is discussed in the rest of the README.\n\nConstexpr Serialization\n-----------------------\nAs was said above, the library is almost completely constexpr, here is an example\nof using array as data object but also using it in compile time to serialize and deserialize\na tuple of integers:\n```cpp\nconstexpr auto tuple_integers()\n{\n    std::array\u003cstd::byte, 0x1000\u003e data{};\n    auto [in, out] = zpp::bits::in_out(data);\n    out(std::tuple{1,2,3,4,5}).or_throw();\n\n    std::tuple t{0,0,0,0,0};\n    in(t).or_throw();\n    return t;\n}\n\n// Compile time check.\nstatic_assert(tuple_integers() == std::tuple{1,2,3,4,5});\n```\n\nFor convenience, the library also provides some simplified serialization functions for\ncompile time:\n```cpp\nusing namespace zpp::bits::literals;\n\n// Returns an array\n// where the first bytes are those of the hello world string and then\n// the 1337 as 4 byte integer.\nconstexpr std::array data =\n    zpp::bits::to_bytes\u003c\"Hello World!\"_s, 1337\u003e();\n\nstatic_assert(\n    zpp::bits::from_bytes\u003cdata,\n                          zpp::bits::string_literal\u003cchar, 12\u003e,\n                          int\u003e() == std::tuple{\"Hello World!\"_s, 1337});\n```\n\nPosition Control\n----------------\nQuery the position of `in` and `out` using `position()`, in other words\nthe bytes read and written respectively:\n```cpp\nstd::size_t bytes_read = in.position();\nstd::size_t bytes_written = out.position();\n```\n\nReset the position backwards or forwards, or to the beginning, use with extreme care:\n```cpp\nin.reset(); // reset to beginning.\nin.reset(position); // reset to position.\nin.position() -= sizeof(int); // Go back an integer.\nin.position() += sizeof(int); // Go forward an integer.\n\nout.reset(); // reset to beginning.\nout.reset(position); // reset to position.\nout.position() -= sizeof(int); // Go back an integer.\nout.position() += sizeof(int); // Go forward an integer.\n```\n\nStandard Library Types Serialization\n------------------------------------\nWhen serializing variable length standard library types, such as vectors,\nstrings and view types such as span and string view, the library\nfirst stores 4 byte integer representing the size, followed by the elements.\n```cpp\nstd::vector v = {1,2,3,4};\nout(v);\nin(v);\n```\nThe reason why the default size type is of 4 bytes (i.e `std::uint32_t`) is for portability between\ndifferent architectures, as well as most programs almost never reach a case of a container being\nmore than 2^32 items, and it may be unjust to pay the price of 8 bytes size by default.\n\nFor specific size types that are not 4 bytes, use `zpp::bits::sized`/`zpp::bits::sized_t` like so:\n```cpp\n// Using `sized` function:\nstd::vector\u003cint\u003e v = {1,2,3,4};\nout(zpp::bits::sized\u003cstd::uint16_t\u003e(v));\nin(zpp::bits::sized\u003cstd::uint16_t\u003e(v));\n\n// Using `sized_t` type:\nzpp::bits::sized_t\u003cstd::vector\u003cint\u003e, std::uint16_t\u003e v = {1,2,3,4};\nout(v);\nin(v);\n```\n\nMake sure that the size type is large enough for the serialized object, otherwise less items\nwill be serialized, according to conversion rules of unsigned types.\n\nYou can also choose to not serialize the size at all, like so:\n```cpp\n// Using `unsized` function:\nstd::vector\u003cint\u003e v = {1,2,3,4};\nout(zpp::bits::unsized(v));\nin(zpp::bits::unsized(v));\n\n// Using `unsized_t` type:\nzpp::bits::unsized_t\u003cstd::vector\u003cint\u003e\u003e v = {1,2,3,4};\nout(v);\nin(v);\n```\n\nFor where it is common, there are alias declarations for sized / unsized versions of types, for example,\nhere are `vector` and `span`, others such as `string`, `string_view`, etc are using the same pattern.\n```cpp\nzpp::bits::vector1b\u003cT\u003e; // vector with 1 byte size.\nzpp::bits::vector2b\u003cT\u003e; // vector with 2 byte size.\nzpp::bits::vector4b\u003cT\u003e; // vector with 4 byte size == default std::vector configuration\nzpp::bits::vector8b\u003cT\u003e; // vector with 8 byte size.\nzpp::bits::static_vector\u003cT\u003e; // unsized vector\nzpp::bits::native_vector\u003cT\u003e; // vector with native (size_type) byte size.\n\nzpp::bits::span1b\u003cT\u003e; // span with 1 byte size.\nzpp::bits::span2b\u003cT\u003e; // span with 2 byte size.\nzpp::bits::span4b\u003cT\u003e; // span with 4 byte size == default std::span configuration\nzpp::bits::span8b\u003cT\u003e; // span with 8 byte size.\nzpp::bits::static_span\u003cT\u003e; // unsized span\nzpp::bits::native_span\u003cT\u003e; // span with native (size_type) byte size.\n```\n\nSerialization of fixed size types such as arrays, `std::array`s, `std::tuple`s don't include\nany overhead except the elements followed by each other.\n\nChanging the default size type for the whole archive is possible during creation:\n```cpp\nzpp::bits::in in(data, zpp::bits::size1b{}); // Use 1 byte for size.\nzpp::bits::out out(data, zpp::bits::size1b{}); // Use 1 byte for size.\n\nzpp::bits::in in(data, zpp::bits::size2b{}); // Use 2 bytes for size.\nzpp::bits::out out(data, zpp::bits::size2b{}); // Use 2 bytes for size.\n\nzpp::bits::in in(data, zpp::bits::size4b{}); // Use 4 bytes for size.\nzpp::bits::out out(data, zpp::bits::size4b{}); // Use 4 bytes for size.\n\nzpp::bits::in in(data, zpp::bits::size8b{}); // Use 8 bytes for size.\nzpp::bits::out out(data, zpp::bits::size8b{}); // Use 8 bytes for size.\n\nzpp::bits::in in(data, zpp::bits::size_native{}); // Use std::size_t for size.\nzpp::bits::out out(data, zpp::bits::size_native{}); // Use std::size_t for size.\n\nzpp::bits::in in(data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting.\nzpp::bits::out out(data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting.\n\n// Can also do it together, for example for 2 bytes size:\nauto [data, in, out] = data_in_out(zpp::bits::size2b{});\nauto [data, out] = data_out(zpp::bits::size2b{});\nauto [data, in] = data_in(zpp::bits::size2b{});\n```\n\nSerialization as Bytes\n----------------------\nMost of the types the library knows how to optimize and serialize objects as bytes.\nIt is however disabled when using explicit serialization functions.\n\nIf you know your type is serializable just as raw bytes, and you are using\nexplicit serialization, you can opt in and optimize\nits serialization to a mere `memcpy`:\n```cpp\nstruct point\n{\n    int x;\n    int y;\n\n    constexpr static auto serialize(auto \u0026 archive, auto \u0026 self)\n    {\n        // Serialize as bytes, instead of serializing each\n        // member separately. The overall result is the same, but this may be\n        // faster sometimes.\n        return archive(zpp::bits::as_bytes(self));\n    }\n};\n```\n\nIt's also possible to do this directly from a vector or span of trivially copyable types,\nthis time we use `bytes` instead of `as_bytes` because we convert the contents of the vector\nto bytes rather than the vector object itself (the data the vector points to rather than the vector object):\n```cpp\nstd::vector\u003cpoint\u003e points;\nout(zpp::bits::bytes(points));\nin(zpp::bits::bytes(points));\n```\nHowever in this case the size is not serialized, this may be extended in the future to also\nsupport serializing the size similar to other view types. If you need to serialize as bytes\nand want the size, as a workaround it's possible to cast to `std::span\u003cstd::byte\u003e`.\n\nVariant Types and Version Control\n---------------------------------\nWhile there is no perfect tool to handle backwards compatibility of structures because\nof the zero overhead-ness of the serialization, you can use `std::variant` as a way\nto version your classes or create a nice polymorphism based dispatching, here is how:\n```cpp\nnamespace v1\n{\nstruct person\n{\n    using serialize = zpp::bits::members\u003c2\u003e;\n\n    auto get_hobby() const\n    {\n        return \"\u003cnone\u003e\"sv;\n    }\n\n    std::string name;\n    int age;\n};\n} // namespace v1\n\nnamespace v2\n{\nstruct person\n{\n    using serialize = zpp::bits::members\u003c3\u003e;\n\n    auto get_hobby() const\n    {\n        return std::string_view(hobby);\n    }\n\n    std::string name;\n    int age;\n    std::string hobby;\n};\n} // namespace v2\n```\n\nAnd then to the serialization itself:\n```cpp\nauto [data, in, out] = zpp::bits::data_in_out();\nout(std::variant\u003cv1::person, v2::person\u003e(v1::person{\"Person1\", 25}))\n    .or_throw();\n\nstd::variant\u003cv1::person, v2::person\u003e v;\nin(v).or_throw();\n\nstd::visit([](auto \u0026\u0026 person) {\n    (void) person.name == \"Person1\";\n    (void) person.age == 25;\n    (void) person.get_hobby() == \"\u003cnone\u003e\";\n}, v);\n\nout(std::variant\u003cv1::person, v2::person\u003e(\n        v2::person{\"Person2\", 35, \"Basketball\"}))\n    .or_throw();\n\nin(v).or_throw();\n\nstd::visit([](auto \u0026\u0026 person) {\n    (void) person.name == \"Person2\";\n    (void) person.age == 35;\n    (void) person.get_hobby() == \"Basketball\";\n}, v);\n```\nThe way the variant gets serialized is by serializing its index (0 or 1) as a `std::byte`\nbefore serializing the actual object. This is very efficient, however sometimes\nusers may want to choose explicit serialization id for that, refer to the point below\n\nTo set a custom serialization id, you need to add an additional line inside/outside your\nclass respectively:\n```cpp\nusing namespace zpp::bits::literals;\n\n// Inside the class, this serializes the full string \"v1::person\" before you serialize\n// the person.\nusing serialize_id = zpp::bits::id\u003c\"v1::person\"_s\u003e;\n\n// Outside the class, this serializes the full string \"v1::person\" before you serialize\n// the person.\nauto serialize_id(const person \u0026) -\u003e zpp::bits::id\u003c\"v1::person\"_s\u003e;\n```\nNote that the serialization ids of types in the variant must match in length, or a\ncompilation error will issue.\n\nYou may also use any sequence of bytes instead of a readable string, as well as an integer\nor any literal type, here is an example of how to use a hash of a string as a serialization\nid:\n```cpp\nusing namespace zpp::bits::literals;\n\n// Inside:\nusing serialize_id = zpp::bits::id\u003c\"v1::person\"_sha1\u003e; // Sha1\nusing serialize_id = zpp::bits::id\u003c\"v1::person\"_sha256\u003e; // Sha256\n\n// Outside:\nauto serialize_id(const person \u0026) -\u003e zpp::bits::id\u003c\"v1::person\"_sha1\u003e; // Sha1\nauto serialize_id(const person \u0026) -\u003e zpp::bits::id\u003c\"v1::person\"_sha256\u003e; // Sha256\n```\n\nYou can also serialize just the first bytes of the hash, like so:\n```cpp\n// First 4 bytes of hash:\nusing serialize_id = zpp::bits::id\u003c\"v1::person\"_sha256, 4\u003e;\n\n// First sizeof(int) bytes of hash:\nusing serialize_id = zpp::bits::id\u003c\"v1::person\"_sha256_int\u003e;\n```\n\nThe type is then converted to bytes at compile time using (... wait for it) `zpp::bits::out`\nat compile time, so as long as your literal type is serializable according to the above,\nyou can use it as a serialization id. The id is serialized to `std::array\u003cstd::byte, N\u003e` however\nfor 1, 2, 4, and 8 bytes its underlying type is `std::byte` `std::uint16_t`, `std::uin32_t` and\n`std::uint64_t` respectively for ease of use and efficiency.\n\nIf you want to serialize the variant without an id, or if you know that a variant is going to\nhave a particular ID upon deserialize, you may do it using `zpp::bits::known_id` to wrap your variant:\n```cpp\nstd::variant\u003cv1::person, v2::person\u003e v;\n\n // Id assumed to be v2::person, and is not serialized / deserialized.\nout(zpp::bits::known_id\u003c\"v2::person\"_sha256_int\u003e(v));\nin(zpp::bits::known_id\u003c\"v2::person\"_sha256_int\u003e(v));\n\n// When deserializing you can pass the id as function parameter, to be able\n// to use outside of compile time context. `id_v` stands for \"id value\".\n// In our case 4 bytes translates to a plain std::uint32_t, so any dynamic\n// integer could fit as the first parameter to `known_id` below.\nin(zpp::bits::known_id(zpp::bits::id_v\u003c\"v2::person\"_sha256_int\u003e, v));\n```\n\nLiteral Operators\n-----------------\nDescription of helper literals in the library:\n```cpp\nusing namespace zpp::bits::literals;\n\n\"hello\"_s // Make a string literal.\n\"hello\"_b // Make a binary data literal.\n\"hello\"_sha1 // Make a sha1 binary data literal.\n\"hello\"_sha256 // Make a sha256 binary data literal.\n\"hello\"_sha1_int // Make a sha1 integer from the first hash bytes.\n\"hello\"_sha256_int // Make a sha256 integer from the first hash bytes.\n\"01020304\"_decode_hex // Decode a hex string into bytes literal.\n```\n\nApply to Functions\n------------------\n* You can apply input archive contents to a function directly, using\n`zpp::bits::apply`, the function must be non-template and have exactly\none overload:\n```cpp\nint foo(std::string s, int i)\n{\n    // s == \"hello\"s;\n    // i == 1337;\n    return 1338;\n}\n\nauto [data, in, out] = zpp::bits::data_in_out();\nout(\"hello\"s, 1337).or_throw();\n\n// Call the foo in one of the following ways:\n\n// Exception based:\nzpp::bits::apply(foo, in).or_throw() == 1338;\n\n// zpp::throwing based:\nco_await zpp::bits::apply(foo, in) == 1338;\n\n// Return value based:\nif (auto result = zpp::bits::apply(foo, in);\n    failure(result)) {\n    // Failure...\n} else {\n    result.value() == 1338;\n}\n```\nWhen your function receives no parameters, the effect is just calling the function\nwithout deserialization and the return value is the return value of your function.\nWhen the function returns void, there is no value for the resulting type.\n\nRemote Procedure Call (RPC)\n---------------------------\nThe library also provides a thin RPC (remote procedure call) interface to allow serializing\nand deserializing function calls:\n```cpp\nusing namespace std::literals;\nusing namespace zpp::bits::literals;\n\nint foo(int i, std::string s);\nstd::string bar(int i, int j);\n\nusing rpc = zpp::bits::rpc\u003c\n    zpp::bits::bind\u003cfoo, \"foo\"_sha256_int\u003e,\n    zpp::bits::bind\u003cbar, \"bar\"_sha256_int\u003e\n\u003e;\n\nauto [data, in, out] = zpp::bits::data_in_out();\n\n// Server and client together:\nauto [client, server] = rpc::client_server(in, out);\n\n// Or separately:\nrpc::client client{in, out};\nrpc::server server{in, out};\n\n// Request from the client:\nclient.request\u003c\"foo\"_sha256_int\u003e(1337, \"hello\"s).or_throw();\n\n// Serve the request from the server:\nserver.serve().or_throw();\n\n// Read back the response\nclient.response\u003c\"foo\"_sha256_int\u003e().or_throw(); // == foo(1337, \"hello\"s);\n```\n\nRegarding error handling, similar to many examples above you can use return value, exceptions,\nor `zpp::throwing` way for handling errors.\n```cpp\n// Return value based.\nif (auto result = client.request\u003c\"foo\"_sha256_int\u003e(1337, \"hello\"s); failure(result)) {\n    // Handle the failure.\n}\nif (auto result = server.serve(); failure(result)) {\n    // Handle the failure.\n}\nif (auto result = client.response\u003c\"foo\"_sha256_int\u003e(); failure(result)) {\n    // Handle the failure.\n} else {\n    // Use response.value();\n}\n\n// Throwing based.\nco_await client.request\u003c\"foo\"_sha256_int\u003e(1337, \"hello\"s); failure(result));\nco_await server.serve();\nco_await client.response\u003c\"foo\"_sha256_int\u003e(); // == foo(1337, \"hello\"s);\n```\n\nIt's possible for the IDs of the RPC calls to be skipped, for example of they\nare passed out of band, here is how to achieve this:\n```cpp\nserver.serve(id); // id is already known, don't deserialize it.\nclient.request_body\u003cId\u003e(arguments...); // request without serializing id.\n```\n\nMember functions can also be registered for RPC, however the server needs\nto get a reference to the class object during construction, and all of the member\nfunctions must belong to the same class (though namespace scope functions are ok to mix):\n```cpp\nstruct a\n{\n    int foo(int i, std::string s);\n};\n\nstd::string bar(int i, int j);\n\nusing rpc = zpp::bits::rpc\u003c\n    zpp::bits::bind\u003c\u0026a::foo, \"a::foo\"_sha256_int\u003e,\n    zpp::bits::bind\u003cbar, \"bar\"_sha256_int\u003e\n\u003e;\n\nauto [data, in, out] = zpp::bits::data_in_out();\n\n// Our object.\na a1;\n\n// Server and client together:\nauto [client, server] = rpc::client_server(in, out, a1);\n\n// Or separately:\nrpc::client client{in, out};\nrpc::server server{in, out, a1};\n\n// Request from the client:\nclient.request\u003c\"a::foo\"_sha256_int\u003e(1337, \"hello\"s).or_throw();\n\n// Serve the request from the server:\nserver.serve().or_throw();\n\n// Read back the response\nclient.response\u003c\"a::foo\"_sha256_int\u003e().or_throw(); // == a1.foo(1337, \"hello\"s);\n```\n\nThe RPC can also work in an opaque mode and let the function itself serialize/deserialize\nthe data, when binding a function as opaque, using `bind_opaque`:\n```cpp\n// Each of the following signatures of `foo()` are valid for opaque rpc call:\nauto foo(zpp::bits::in\u003c\u003e \u0026, zpp::bits::out\u003c\u003e \u0026);\nauto foo(zpp::bits::in\u003c\u003e \u0026);\nauto foo(zpp::bits::out\u003c\u003e \u0026);\nauto foo(std::span\u003cstd::byte\u003e input); // assumes all data is consumed from archive.\nauto foo(std::span\u003cstd::byte\u003e \u0026 input); // resize input in the function to signal how much was consumed.\n\nusing rpc = zpp::bits::rpc\u003c\n    zpp::bits::bind_opaque\u003cfoo, \"a::foo\"_sha256_int\u003e,\n    zpp::bits::bind\u003cbar, \"bar\"_sha256_int\u003e\n\u003e;\n```\n\nByte Order Customization\n------------------------\nThe default byte order used is the native processor/OS selected one.\nYou may choose another byte order using `zpp::bits::endian` during construction like so:\n```cpp\nzpp::bits::in in(data, zpp::bits::endian::big{}); // Use big endian\nzpp::bits::out out(data, zpp::bits::endian::big{}); // Use big endian\n\nzpp::bits::in in(data, zpp::bits::endian::network{}); // Use big endian (provided for convenience)\nzpp::bits::out out(data, zpp::bits::endian::network{}); // Use big endian (provided for convenience)\n\nzpp::bits::in in(data, zpp::bits::endian::little{}); // Use little endian\nzpp::bits::out out(data, zpp::bits::endian::little{}); // Use little endian\n\nzpp::bits::in in(data, zpp::bits::endian::swapped{}); // If little use big otherwise little.\nzpp::bits::out out(data, zpp::bits::endian::swapped{}); // If little use big otherwise little.\n\nzpp::bits::in in(data, zpp::bits::endian::native{}); // Use the native one (default).\nzpp::bits::out out(data, zpp::bits::endian::native{}); // Use the native one (default).\n\n// Can also do it together, for example big endian:\nauto [data, in, out] = data_in_out(zpp::bits::endian::big{});\nauto [data, out] = data_out(zpp::bits::endian::big{});\nauto [data, in] = data_in(zpp::bits::endian::big{});\n```\n\nDeserializing Views Of Const Bytes\n----------------------------------\nOn the receiving end (input archive), the library supports view types of const byte types, such\nas `std::span\u003cconst std::byte\u003e` in order to get a view at a portion of data without copying.\nThis needs to be carefully used because invalidating iterators of the contained data could cause\na use after free. It is provided to allow the optimization when needed:\n```cpp\nusing namespace std::literals;\n\nauto [data, in, out] = zpp::bits::data_in_out();\nout(\"hello\"sv).or_throw();\n\nstd::span\u003cconst std::byte\u003e s;\nin(s).or_throw();\n\n// s.size() == \"hello\"sv.size()\n// std::memcmp(\"hello\"sv.data(), s.data(), \"hello\"sv.size()) == 0\n}\n```\n\nThere is also an unsized version, which consumes the rest of the archive data\nto allow the common use case of header then arbitrary amount of data:\n```cpp\nauto [data, in, out] = zpp::bits::data_in_out();\nout(zpp::bits::unsized(\"hello\"sv)).or_throw();\n\nstd::span\u003cconst std::byte\u003e s;\nin(zpp::bits::unsized(s)).or_throw();\n\n// s.size() == \"hello\"sv.size()\n// std::memcmp(\"hello\"sv.data(), s.data(), \"hello\"sv.size()) == 0\n```\n\nPointers as Optionals\n---------------------\nThe library does not support serializing null pointer values, however to explicitly support\noptional owning pointers, such as to create graphs and complex structures.\n\nIn theory it's valid to use `std::optional\u003cstd::unique_ptr\u003cT\u003e\u003e`, but it's\nrecommended to use the specifically made `zpp::bits::optional_ptr\u003cT\u003e` which\noptimizes out the boolean that the optional object usually keeps, and uses null pointer\nas an invalid state.\n\nSerializing a null pointer value in that case will serialize a zero byte, while\nnon-null values serialize as a single one byte followed by the bytes of the object.\n(i.e, serialization is identical to `std::optional\u003cT\u003e`).\n\nReflection\n----------\nAs part of the library implementation it was required to implement some reflection types, for\ncounting members and visiting members, and the library exposes these to the user:\n```cpp\nstruct point\n{\n    int x;\n    int y;\n};\n\n#if !ZPP_BITS_AUTODETECT_MEMBERS_MODE\nauto serialize(point) -\u003e zpp::bits::members\u003c2\u003e;\n#endif\n\nstatic_assert(zpp::bits::number_of_members\u003cpoint\u003e() == 2);\n\nconstexpr auto sum = zpp::bits::visit_members(\n    point{1, 2}, [](auto x, auto y) { return x + y; });\n\nstatic_assert(sum == 3);\n\nconstexpr auto generic_sum = zpp::bits::visit_members(\n    point{1, 2}, [](auto... members) { return (0 + ... + members); });\n\nstatic_assert(generic_sum == 3);\n\nconstexpr auto is_two_integers =\n    zpp::bits::visit_members_types\u003cpoint\u003e([]\u003ctypename... Types\u003e() {\n        if constexpr (std::same_as\u003cstd::tuple\u003cTypes...\u003e,\n                                   std::tuple\u003cint, int\u003e\u003e) {\n            return std::true_type{};\n        } else {\n            return std::false_type{};\n        }\n    })();\n\nstatic_assert(is_two_integers);\n```\nThe example above works with or without `ZPP_BITS_AUTODETECT_MEMBERS_MODE=1`, depending\non the `#if`. As noted above, we must rely on specific compiler feature to detect the\nnumber of members which may not be portable.\n\nAdditional Archive Controls\n---------------------------\nArchives can be constructed with additional control options such as `zpp::bits::append{}`\nwhich instructs output archives to set the position to the end of the vector or other data\nsource. (for input archives this option has no effect)\n```cpp\nstd::vector\u003cstd::byte\u003e data;\nzpp::bits::out out(data, zpp::bits::append{});\n```\n\nIt is possible to use multiple controls and to use them also with `data_in_out/data_in/data_out/in_out`:\n```cpp\nzpp::bits::out out(data, zpp::bits::append{}, zpp::bits::endian::big{});\nauto [in, out] = in_out(data, zpp::bits::append{}, zpp::bits::endian::big{});\nauto [data, in, out] = data_in_out(zpp::bits::size2b{}, zpp::bits::endian::big{});\n```\n\nAllocation size can be limited in case of output archive to a growing buffer\nor when using an input archive to limit how long a single length prefixed\nmessage can be to avoid allocation of a very large buffer in advance, using `zpp::bits::alloc_limit\u003cL\u003e{}`.\nThe intended use is for safety and sanity reasons rather than\naccurate allocation measurement:\n```cpp\nzpp::bits::out out(data, zpp::bits::alloc_limit\u003c0x10000\u003e{});\nzpp::bits::in in(data, zpp::bits::alloc_limit\u003c0x10000\u003e{});\nauto [in, out] = in_out(data, zpp::bits::alloc_limit\u003c0x10000\u003e{});\nauto [data, in, out] = data_in_out(zpp::bits::alloc_limit\u003c0x10000\u003e{});\n```\n\nFor best correctness, when using growing buffer for output, if the buffer was grown, the buffer is resized\nin the end for the exact position of the output archive, this incurs an extra resize\nwhich in most cases is acceptable, but you may avoid this additional resize and recognize\nthe end of the buffer by using `position()`. You can achieve this by using `zpp::bits::no_fit_size{}`:\n```cpp\nzpp::bits::out out(data, zpp::bits::no_fit_size{});\n```\n\nTo control enlarging of output archive vector, you may use `zpp::bits::enlarger\u003cMul, Div = 1\u003e`:\n```cpp\nzpp::bits::out out(data, zpp::bits::enlarger\u003c2\u003e{}); // Grow by multiplying size by 2.\nzpp::bits::out out(data, zpp::bits::enlarger\u003c3, 2\u003e{}); // Default - Grow by multiplying size by 3 and divide by 2 (enlarge by 1.5).\nzpp::bits::out out(data, zpp::bits::exact_enlarger{}); // Grow to exact size every time.\n```\n\nBy default, for safety, an output archive that uses a growing buffer, checks for overflow\nbefore any buffer grow. For 64 bit systems, this check although cheap, is almost redundant,\nas it is almost impossible to overflow a 64 bit integer when it represents a memory size. (i.e,\nthe memory allocation will fail before the memory comes close to overflow this integer).\nIf you wish to disable those overflow checks, in favor of performance, use: `zpp::bits::no_enlarge_overflow{}`:\n```cpp\nzpp::bits::out out(data, zpp::bits::no_enlarge_overflow{}); // Disable overflow check when enlarging.\n```\n\nWhen serializing explicitly it is often required to identify whether the archive is\ninput or output archive, and it is done via the `archive.kind()` static member function,\nand can be done in an `if constexpr`:\n```cpp\nstatic constexpr auto serialize(auto \u0026 archive, auto \u0026 self)\n{\n    using archive_type = std::remove_cvref_t\u003cdecltype(archive)\u003e;\n\n    if constexpr (archive_type::kind() == zpp::bits::kind::in) {\n        // Input archive\n    } else if constexpr (archive_type::kind() == zpp::bits::kind::out) {\n        // Output archive\n    } else {\n        // No such archive (no need to check for this)\n    }\n}\n```\n\nVariable Length Integers\n------------------------\nThe library provides a type for serializing and deserializing variable\nlength integers:\n```cpp\nauto [data, in, out] = zpp::bits::data_in_out();\nout(zpp::bits::varint{150}).or_throw();\n\nzpp::bits::varint i{0};\nin(i).or_throw();\n\n// i == 150;\n```\n\nHere is an example of the encoding at compile time:\n```cpp\nstatic_assert(zpp::bits::to_bytes\u003czpp::bits::varint{150}\u003e() == \"9601\"_decode_hex);\n```\n\nThe class template `zpp::bits::varint\u003cT, E = varint_encoding::normal\u003e` is provided\nto be able to define any varint integral type or enumeration type,\nalong with possible encodings `zpp::bits::varint_encoding::normal/zig_zag` (normal is default).\n\nThe following alias declarations are provided:\n```cpp\nusing vint32_t = varint\u003cstd::int32_t\u003e; // varint of int32 types.\nusing vint64_t = varint\u003cstd::int64_t\u003e; // varint of int64 types.\n\nusing vuint32_t = varint\u003cstd::uint32_t\u003e; // varint of unsigned int32 types.\nusing vuint64_t = varint\u003cstd::uint64_t\u003e; // varint of unsigned int64 types.\n\nusing vsint32_t = varint\u003cstd::int32_t, varint_encoding::zig_zag\u003e; // zig zag encoded varint of int32 types.\nusing vsint64_t = varint\u003cstd::int64_t, varint_encoding::zig_zag\u003e; // zig zag encoded varint of int64 types.\n\nusing vsize_t = varint\u003cstd::size_t\u003e; // varint of std::size_t types.\n```\n\nUsing varints to serialize sizes by default is also possible during archive creation:\n```cpp\nauto [data, in, out] = data_in_out(zpp::bits::size_varint{});\n\nzpp::bits::in in(data, zpp::bits::size_varint{}); // Uses varint to encode size.\nzpp::bits::out out(data, zpp::bits::size_varint{}); // Uses varint to encode size.\n```\n\nProtobuf\n--------\nThe serialization format of this library is not based on any known or accepted format.\nNaturally, other languages do not support this format, which makes it near impossible to use\nthe library for cross programming language communication.\n\nFor this reason the library supports the protobuf format\nwhich is available in many languages.\n\nPlease note that protobuf support is kind of experimental, which means\nit may not include every possible protobuf feature, and it is generally slower\n(around 2-5 times slower, mostly on deserialization) than the default format,\nwhich aims to be zero overhead.\n\nStarting with the basic message:\n```cpp\nstruct example\n{\n    zpp::bits::vint32_t i; // varint of 32 bit, field number is implicitly set to 1,\n    // next field is implicitly 2, and so on\n};\n\n// Serialize as protobuf protocol (as usual, can also define this inside the class\n// with `using serialize = zpp::bits::pb_protocol;`)\nauto serialize(const example \u0026) -\u003e zpp::bits::pb_protocol;\n\n// Use archives as usual, specify what kind of size to prefix the message with.\n// We chose no size to demonstrate the actual encoding of the message, but in general\n// it is recommended to size prefix protobuf messages since they are not self terminating.\nauto [data, in, out] = data_in_out(zpp::bits::no_size{});\n\nout(example{.i = 150}).or_throw();\n\nexample e;\nin(e).or_throw();\n\n// e.i == 150\n\n// Serialize the message without any size prefix, and check the encoding at compile time:\nstatic_assert(\n    zpp::bits::to_bytes\u003czpp::bits::unsized_t\u003cexample\u003e{{.i = 150}}\u003e() ==\n    \"089601\"_decode_hex);\n```\n\nFor the full syntax, which we'll later use to pass more options, use `zpp::bits::protocol`:\n```cpp\n// Serialize as protobuf protocol (as usual, can also define this inside the class\n// with `using serialize = zpp::bits::protocol\u003czpp::bits::pb{}\u003e;`)\nauto serialize(const example \u0026) -\u003e zpp::bits::protocol\u003czpp::bits::pb{}\u003e;\n```\n\nTo reserve fields:\n```cpp\nstruct example\n{\n    [[no_unique_address]] zpp::bits::pb_reserved _1; // field number 1 is reserved.\n    zpp::bits::vint32_t i; // field number == 2\n    zpp::bits::vsint32_t j; // field number == 3\n};\n```\n\nTo explicitly specify for each member the field number:\n```cpp\nstruct example\n{\n    zpp::bits::pb_field\u003czpp::bits::vint32_t, 20\u003e i; // field number == 20\n    zpp::bits::pb_field\u003czpp::bits::vsint32_t, 30\u003e j; // field number == 30\n\n    using serialize = zpp::bits::pb_protocol;\n};\n```\nAccessing the value behind the field is often transparent however if explicitly needed\nuse `pb_value(\u003cvariable\u003e)` to get or assign to the value.\n\nTo map members to another field number:\n```cpp\nstruct example\n{\n    zpp::bits::vint32_t i; // field number == 20\n    zpp::bits::vsint32_t j; // field number == 30\n\n    using serialize = zpp::bits::protocol\u003c\n        zpp::bits::pb{\n            zpp::bits::pb_map\u003c1, 20\u003e{}, // Map first member to field number 20.\n            zpp::bits::pb_map\u003c2, 30\u003e{}}\u003e; // Map second member to field number 30.\n};\n```\n\nFixed members are simply regular C++ data members:\n```cpp\nstruct example\n{\n    std::uint32_t i; // fixed unsigned integer 32, field number == 1\n};\n```\n\nLike with `zpp::bits::members`, for when it is required, you may specify the number of members\nin the protocol field with `zpp::bits::pb_members\u003cN\u003e`:\n```cpp\nstruct example\n{\n    using serialize = zpp::bits::pb_members\u003c1\u003e; // 1 member.\n\n    zpp::bits::vint32_t i; // field number == 1\n};\n```\n\nThe full version of the above involves passing the number of members\nas the second parameter to the protocol:\n```cpp\nstruct example\n{\n    using serialize = zpp::bits::protocol\u003czpp::bits::pb{}, 1\u003e; // 1 member.\n\n    zpp::bits::vint32_t i; // field number == 1\n};\n```\n\nEmbedded messages are simply nested within the class as data members:\n```cpp\nstruct nested_example\n{\n    example nested; // field number == 1\n};\n\nauto serialize(const nested_example \u0026) -\u003e zpp::bits::pb_protocol;\n\nstatic_assert(zpp::bits::to_bytes\u003czpp::bits::unsized_t\u003cnested_example\u003e{\n                  {.nested = example{150}}}\u003e() == \"0a03089601\"_decode_hex);\n```\n\nRepeated fields are of the form of owning containers:\n```cpp\nstruct repeating\n{\n    using serialize = zpp::bits::pb_protocol;\n\n    std::vector\u003czpp::bits::vint32_t\u003e integers; // field number == 1\n    std::string characters; // field number == 2\n    std::vector\u003cexample\u003e examples; // repeating examples, field number == 3\n};\n```\n\nCurrently all of the fields are optional, which is a good practice, missing fields are dropped and not concatenated to the message, for efficiency.\nAny value that is not set in a message leaves the target data member intact, which allows\nto implement defaults for data members by using non-static data member initializer or to initialize\nthe data member before deserializing the message.\n\nLets take a full `.proto` file and translate it:\n```proto\nsyntax = \"proto3\";\n\npackage tutorial;\n\nmessage person {\n  string name = 1;\n  int32 id = 2;\n  string email = 3;\n\n  enum phone_type {\n    mobile = 0;\n    home = 1;\n    work = 2;\n  }\n\n  message phone_number {\n    string number = 1;\n    phone_type type = 2;\n  }\n\n  repeated phone_number phones = 4;\n}\n\nmessage address_book {\n  repeated person people = 1;\n}\n```\n\nThe translated file:\n```cpp\nstruct person\n{\n    std::string name; // = 1\n    zpp::bits::vint32_t id; // = 2\n    std::string email; // = 3\n\n    enum phone_type\n    {\n        mobile = 0,\n        home = 1,\n        work = 2,\n    };\n\n    struct phone_number\n    {\n        std::string number; // = 1\n        phone_type type; // = 2\n    };\n\n    std::vector\u003cphone_number\u003e phones; // = 4\n};\n\nstruct address_book\n{\n    std::vector\u003cperson\u003e people; // = 1\n};\n\nauto serialize(const person \u0026) -\u003e zpp::bits::pb_protocol;\nauto serialize(const person::phone_number \u0026) -\u003e zpp::bits::pb_protocol;\nauto serialize(const address_book \u0026) -\u003e zpp::bits::pb_protocol;\n```\n\nDerserializing a message that was originally serialized with python:\n```python\nimport addressbook_pb2\nperson = addressbook_pb2.person()\nperson.id = 1234\nperson.name = \"John Doe\"\nperson.email = \"jdoe@example.com\"\nphone = person.phones.add()\nphone.number = \"555-4321\"\nphone.type = addressbook_pb2.person.home\n```\n\nThe output we get for `person` is:\n```python\nname: \"John Doe\"\nid: 1234\nemail: \"jdoe@example.com\"\nphones {\n  number: \"555-4321\"\n  type: home\n}\n```\n\nLets serialize it:\n```python\nperson.SerializeToString()\n```\n\nThe result is:\n```python\nb'\\n\\x08John Doe\\x10\\xd2\\t\\x1a\\x10jdoe@example.com\"\\x0c\\n\\x08555-4321\\x10\\x01'\n```\n\nBack to C++:\n```cpp\nusing namespace zpp::bits::literals;\n\nconstexpr auto data =\n    \"\\n\\x08John Doe\\x10\\xd2\\t\\x1a\\x10jdoe@example.com\\\"\\x0c\\n\\x08\"\n    \"555-4321\\x10\\x01\"_b;\nstatic_assert(data.size() == 45);\n\nperson p;\nzpp::bits::in{data, zpp::bits::no_size{}}(p).or_throw();\n\n// p.name == \"John Doe\"\n// p.id == 1234\n// p.email == \"jdoe@example.com\"\n// p.phones.size() == 1\n// p.phones[0].number == \"555-4321\"\n// p.phones[0].type == person::home\n```\n\nAdvanced Controls\n-----------------\nBy default `zpp::bits` inlines aggressively, but to reduce code size, it does not\ninline the full decoding of varints (variable length integers).\nTo configure inlining of the full varint decoding, define `ZPP_BITS_INLINE_DECODE_VARINT=1`.\n\nIf you suspect that `zpp::bits` is inlining too much to the point where it badly affects code size,\nyou may define `ZPP_BITS_INLINE_MODE=0`, which disables all force inlining and observe the results.\nUsually it has a negligible effect, but it is provided as is for additional control.\n\nIn some compilers, you may find always inline to fail with recursive structures (for example a tree graph).\nIn these cases it is required to somehow avoid the always inline attribute for the specific structure, a trivial\nexample would be to use an explicit serialization function, although most times the library detects such occasions and\nit is not necessary, but the example is provided just in case:\n```cpp\nstruct node\n{\n    constexpr static auto serialize(auto \u0026 archive, auto \u0026 node)\n    {\n        return archive(node.value, node.nodes);\n    }\n\n    int value;\n    std::vector\u003cnode\u003e nodes;\n};\n```\n\nBenchmark\n---------\n### [fraillt/cpp_serializers_benchmark](https://github.com/fraillt/cpp_serializers_benchmark/tree/a4c0ebfb083c3b07ad16adc4301c9d7a7951f46e)\n#### GCC 11\n| library     | test case                                                  | bin size | data size | ser time | des time |\n| ----------- | ---------------------------------------------------------- | -------- | --------- | -------- | -------- |\n| zpp_bits    | general                                                    | 52192B   | 8413B     | **733ms**| **693ms**|\n| zpp_bits    | fixed buffer                                               | 48000B   | 8413B     | **620ms**| **667ms**|\n| bitsery     | general                                                    | 70904B   | 6913B     | 1470ms   | 1524ms   |\n| bitsery     | fixed buffer                                               | 53648B   | 6913B     | 927ms    | 1466ms   |\n| boost       | general                                                    | 279024B  | 11037B    | 15126ms  | 12724ms  |\n| cereal      | general                                                    | 70560B   | 10413B    | 10777ms  | 9088ms   |\n| flatbuffers | general                                                    | 70640B   | 14924B    | 8757ms   | 3361ms   |\n| handwritten | general                                                    | 47936B   | 10413B    | 1506ms   | 1577ms   |\n| handwritten | unsafe                                                     | 47944B   | 10413B    | 1616ms   | 1392ms   |\n| iostream    | general                                                    | 53872B   | 8413B     | 11956ms  | 12928ms  |\n| msgpack     | general                                                    | 89144B   | 8857B     | 2770ms   | 14033ms  |\n| protobuf    | general                                                    | 2077864B | 10018B    | 19929ms  | 20592ms  |\n| protobuf    | arena                                                      | 2077872B | 10018B    | 10319ms  | 11787ms  |\n| yas         | general                                                    | 61072B   | 10463B    | 2286ms   | 1770ms   |\n\n#### Clang 12.0.1\n| library     | test case                                                  | bin size | data size | ser time | des time |\n| ----------- | ---------------------------------------------------------- | -------- | --------- | -------- | -------- |\n| zpp_bits    | general                                                    | 47128B   | 8413B     | **790ms**| **715ms**|\n| zpp_bits    | fixed buffer                                               | 43056B   | 8413B     | **605ms**| **694ms**|\n| bitsery     | general                                                    | 53728B   | 6913B     | 2128ms   | 1832ms   |\n| bitsery     | fixed buffer                                               | 49248B   | 6913B     | 946ms    | 1941ms   |\n| boost       | general                                                    | 237008B  | 11037B    | 16011ms  | 13017ms  |\n| cereal      | general                                                    | 61480B   | 10413B    | 9977ms   | 8565ms   |\n| flatbuffers | general                                                    | 62512B   | 14924B    | 9812ms   | 3472ms   |\n| handwritten | general                                                    | 43112B   | 10413B    | 1391ms   | 1321ms   |\n| handwritten | unsafe                                                     | 43120B   | 10413B    | 1393ms   | 1212ms   |\n| iostream    | general                                                    | 48632B   | 8413B     | 10992ms  | 12771ms  |\n| msgpack     | general                                                    | 77384B   | 8857B     | 3563ms   | 14705ms  |\n| protobuf    | general                                                    | 2032712B | 10018B    | 18125ms  | 20211ms  |\n| protobuf    | arena                                                      | 2032760B | 10018B    | 9166ms   | 11378ms  |\n| yas         | general                                                    | 51000B   | 10463B    | 2114ms   | 1558ms   |\n\nLimitations\n-----------\n* Serialization of non-owning pointers \u0026 raw pointers is not supported, for simplicity and also for security reasons.\n* Serialization of null pointers is not supported to avoid the default overhead of stating whether a pointer is null, to\nwork around this use optional which is more explicit.\n\nFinal Words\n-----------\nI wish that you find this library useful.\nPlease feel free to submit any issues, make suggestions for improvements, etc.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyalz800%2Fzpp_bits","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feyalz800%2Fzpp_bits","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyalz800%2Fzpp_bits/lists"}