From 8964bdc1e908f16e4f227db5c191befeb4213a9d Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Sun, 20 Oct 2024 18:12:09 -0500 Subject: [PATCH 1/3] templatize expected write interface --- include/hpp_proto/json_serializer.hpp | 6 +++--- include/hpp_proto/memory_resource_utils.hpp | 20 +++++++++++++----- include/hpp_proto/pb_serializer.hpp | 4 ++-- .../non_owning/non_owning_tutorial_proto3.cpp | 21 +++++++++---------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/include/hpp_proto/json_serializer.hpp b/include/hpp_proto/json_serializer.hpp index 97657de..d59a3cd 100644 --- a/include/hpp_proto/json_serializer.hpp +++ b/include/hpp_proto/json_serializer.hpp @@ -737,11 +737,11 @@ inline json_status write_json(auto const &value, auto &buffer) noexcept { return write_json(value, buffer, ctx); } -template +template inline auto write_json(auto const &value, - glz::is_context auto &&...ctx) noexcept -> glz::expected { + glz::is_context auto &&...ctx) noexcept -> glz::expected { static_assert(sizeof...(ctx) <= 1); - std::string buffer{}; + auto buffer = detail::make_buffer(ctx...); auto ec = write_json(value, buffer, ctx...); if (!ec.ok()) { return glz::unexpected(ec); diff --git a/include/hpp_proto/memory_resource_utils.hpp b/include/hpp_proto/memory_resource_utils.hpp index 9b1007a..f641a3b 100644 --- a/include/hpp_proto/memory_resource_utils.hpp +++ b/include/hpp_proto/memory_resource_utils.hpp @@ -161,12 +161,22 @@ auto &get_memory_resource(T &v) { template using memory_resource_type = std::remove_reference_t))>; -namespace concepts { -template -concept context_with_memory_resource = requires(T &v) { get_memory_resource(v); }; -} // namespace concepts - namespace detail { + +template +Buffer make_buffer() { + return Buffer{}; +} + +template +Buffer make_buffer(auto &&ctx) { + if constexpr (requires { Buffer(&ctx.get_memory_resource()); }) { + return Buffer(&ctx.get_memory_resource()); + } else { + return Buffer{}; + } +} + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) template struct raw_data_iterator { diff --git a/include/hpp_proto/pb_serializer.hpp b/include/hpp_proto/pb_serializer.hpp index c9724b0..d74d356 100644 --- a/include/hpp_proto/pb_serializer.hpp +++ b/include/hpp_proto/pb_serializer.hpp @@ -2581,8 +2581,8 @@ consteval auto write_proto(F make_object) { } template > -expected write_proto(concepts::has_meta auto const &msg) { - Buffer buffer; +expected write_proto(concepts::has_meta auto const &msg, concepts::is_pb_context auto &&...ctx) { + auto buffer = detail::make_buffer(ctx...); auto r = pb_serializer::serialize(msg, buffer); if (auto result = pb_serializer::serialize(msg, buffer); !result.ok()) { return unexpected(r.ec); diff --git a/tutorial/non_owning/non_owning_tutorial_proto3.cpp b/tutorial/non_owning/non_owning_tutorial_proto3.cpp index 76c018d..ad03c38 100644 --- a/tutorial/non_owning/non_owning_tutorial_proto3.cpp +++ b/tutorial/non_owning/non_owning_tutorial_proto3.cpp @@ -21,23 +21,23 @@ inline std::string_view string_dup(std::string_view str, std::pmr::monotonic_buf int main() { using enum tutorial::Person::PhoneType; using namespace std::string_view_literals; - using namespace std::string_literals; std::pmr::monotonic_buffer_resource pool; tutorial::AddressBook address_book; + std::pmr::vector alex_phones{&pool}; + alex_phones.push_back({.number = "19890604"sv, .type = PHONE_TYPE_MOBILE}); + + using PhoneNumberSpan = hpp::proto::equality_comparable_span; + std::pmr::vector people{&pool}; people.resize(2); people[0].name = "Alex"sv; people[0].id = 1; people[0].email = "alex@email.com"sv; - - std::pmr::vector alex_phones{&pool}; - alex_phones.resize(1); - alex_phones[0].number = "19890604"sv; - alex_phones[0].type = PHONE_TYPE_MOBILE; people[0].phones = alex_phones; people[0].nested_message = {{.bb = 89}}; + std::pmr::vector> map_string_nested_message( {{"Tiananmen", {.bb = 89}}, {"Square", {.bb = 64}}}, &pool); people[0].map_string_nested_message = map_string_nested_message; @@ -54,11 +54,10 @@ int main() { address_book.people = people; - std::pmr::vector buffer{&pool}; - - expect(hpp::proto::write_proto(address_book, buffer).ok()); + auto write_result = hpp::proto::write_proto>(address_book, hpp::proto::pb_context{pool}); + expect(write_result.has_value()); - auto read_result = hpp::proto::read_proto(buffer, hpp::proto::pb_context{pool}); + auto read_result = hpp::proto::read_proto(write_result.value(), hpp::proto::pb_context{pool}); expect(read_result.has_value()); expect(address_book == read_result.value()); @@ -90,7 +89,7 @@ int main() { "https://en.wikipedia.org/wiki/1989_Tiananmen_Square_protests_and_massacre"); } - auto write_json_result = hpp::proto::write_json(address_book); + auto write_json_result = hpp::proto::write_json(address_book, hpp::proto::json_context{pool}); expect(write_json_result.has_value()); auto read_json_result = hpp::proto::read_json(write_json_result.value(), hpp::proto::json_context{pool}); From b7b9126aa55ac641b2265cb10db35db62bd813bd Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Sun, 20 Oct 2024 18:13:10 -0500 Subject: [PATCH 2/3] more documentation changes --- README.md | 233 +++++++++++++++++++++++++++++++--- docs/Code_Generation_Guide.md | 200 +++++++++++++++++++---------- 2 files changed, 351 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index d633c13..c21944e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Hpp-proto is a lightweight, high-performance Protocol Buffers implementation for C++20. It maps Protocol Buffers messages directly to simple C++ aggregates, using only C++ built-in or standard library types. Apart from UTF-8 validation, the serialization code for these mapped aggregates is entirely header-only, ensuring minimal dependencies and efficient performance. +Compared to Google’s implementation, hpp-proto features a minimalistic design that significantly reduces code size while delivering superior performance in our benchmarks when runtime reflection is not required. This makes hpp-proto an ideal choice for performance-critical, resource-constrained environments where minimizing binary size is a priority. # Features * Significantly smaller code size compared to Google's implementation. @@ -12,8 +13,9 @@ Hpp-proto is a lightweight, high-performance Protocol Buffers implementation for * Aside from [UTF-8 validation](https://github.com/simdutf/is_utf8), all generated code and the core library are header-only. * Each generated C++ aggregate is associated with static C++ reflection data for efficient Protocol Buffers encoding and decoding. * Includes metadata for JSON serialization in each generated C++ aggregate, utilizing a slightly modified version of the [glaze](https://github.com/stephenberry/glaze) library. +* All generated message types are equality-comparable, making them useful in unit testing. * Completely exception-free. -* Supports non-owning mode code generation, mapping string and repeated fields to `std::string_view` and `std::span`. +* Supports non-owning mode code generation, mapping string and repeated fields to `std::string_view` and `hpp::proto::equality_comparable_span` which derives from `std::span` and adds the equality comparator. * Enables compile-time serialization. ## Limitations @@ -177,28 +179,29 @@ We compared the code sizes of three equivalent programs: [hpp_proto_decode_encod The comparison highlights a significant reduction in code size when using hpp-proto compared to Google’s Protocol Buffers implementations. On macOS, hpp-proto offers a 22.99x reduction in size compared to google_decode_encode and a 9.68x reduction compared to google_decode_encode_lite. The reduction is even more pronounced on Linux, where hpp-proto reduces the code size by 39.13x compared to google_decode_encode and by 16.91x compared to google_decode_encode_lite. -This drastic reduction is a result of hpp-proto’s minimalistic design, which avoids the overhead associated with Google’s full libprotobuf and libprotobuf-lite libraries. The smaller code size makes hpp-proto an attractive option for performance-critical and resource-constrained environments where minimizing binary size is essential. ## Getting Started -This section provides a quick introduction to the basic usage of hpp-proto to help you get started with minimal setup. It covers the essential steps required to integrate hpp-proto into your project and begin working with Protocol Buffers. For more advanced usage scenarios, optimizations, and additional features, please refer to the detailed examples and guides in the tutorial directory of the repository. +This section provides a quick introduction to the basic usage of hpp-proto to help you get started with minimal setup. It covers the essential steps required to integrate hpp-proto into your project and begin working with Protocol Buffers. For more advanced usage scenarios, optimizations, and additional features, please refer to the detailed examples in the [tutorial](tutorial) directory and [code generation guide](docs/Code_Generation_Guide.md) of the repository. ### Install google protoc If you haven’t installed the `protoc` compiler, [download the package](https://protobuf.dev/downloads) and follow the instructions in the README. ### [optional] Install hpp-proto -Hpp-proto can be directly installed locally then use cmake `find_package` to solve the dependency, or it can be used via cmake `FetchContent` mechanism. +The hpp-proto library can be directly installed locally then use cmake `find_package` to solve the dependency, or it can be used via cmake `FetchContent` mechanism. ```bash git clone https://github.com/huangminghuang/hpp-proto.git cd hpp-proto -# use installed protoc by default or specify '-DHPP_PROTO_PROTOC=compile' to compile protoc +# use installed protoc by default or specify '-DHPP_PROTO_PROTOC=compile' to download google protobuf and compile protoc from source cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/local -Bbuild -S . cmake --build build --target install ``` ### Defining Your Protocol Format + ```protobuf +// addressbook.proto syntax = "proto3"; package tutorial; @@ -244,7 +247,9 @@ This generates the following files in your specified destination directory: #### Code generation with CMake -##### find_package +
find_package +

+ ```cmake cmake_minimum_required(VERSION 3.24) @@ -257,13 +262,20 @@ find_package(hpp_proto CONFIG REQUIRED) add_library(addressbook_lib INTERFACE addressbook.proto) target_include_directories(addressbook_lib INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) protobuf_generate(TARGET addressbook_lib - LANGUAGE hpp) + LANGUAGE hpp +# uncomment the next line for non-owning mode +# PLUGIN_OPTIONS non_owning +) add_executable(tutorial_proto addressbook.cpp) target_link_libraries(tutorial_proto PRIVATE addressbook_lib) ``` +

+
+ +
FetchContent +

-#### FetchContent ```cmake cmake_minimum_required(VERSION 3.24) @@ -284,17 +296,27 @@ FetchContent_MakeAvailable(hpp_proto) add_library(addressbook_lib INTERFACE addressbook.proto) target_include_directories(addressbook_lib INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) protobuf_generate(TARGET addressbook_lib - LANGUAGE hpp) + LANGUAGE hpp +# uncomment the next line for non-owning mode +# PLUGIN_OPTIONS non_owning +) add_executable(tutorial_proto addressbook.cpp) target_link_libraries(tutorial_proto PRIVATE addressbook_lib) ``` +

+
## The hpp-proto API The mapping from proto messages to their C++ counterparts is straight forward, as shown in the following generated code. Notice that the `*.msg.hpp` only contains the minimum message definitions and avoid the inclusion of headers related to the protobuf/JSON encoding/decoding facilities. This makes those generated structures easier to be used as basic vocabulary types among modules without incurring unnecessary dependencies. + +Below are the examples of the generated code for the `addressbook.proto` file in regular and non-owning modes. +
Regular Mode +

+ ```cpp // addressbook.msg.hpp namespace tutorial { @@ -327,31 +349,95 @@ struct AddressBook { bool operator == (const AddressBook&) const = default; }; +} + +// addressbook.pb.hpp +#include "addressbook.msg.hpp" +namespace tutorial { + auto pb_meta(const Person &) -> std::tuple<...> ; + auto pb_meta(const Person::PhoneNumber &) -> std::tuple<...> ; + auto pb_meta(const AddressBook &) -> std::tuple<...>; +} +``` +

+
+
Non-owning Mode +

+ +```cpp +// addressbook.msg.hpp +namespace tutorial { + +using namespace hpp::proto::literals; +struct Person { + enum class PhoneType { + MOBILE = 0, + HOME = 1, + WORK = 2 + }; + + struct PhoneNumber { + std::string_view number = {}; + PhoneType type = PhoneType::MOBILE; + + bool operator == (const PhoneNumber&) const = default; + }; + + std::string_view name = {}; + int32_t id = {}; + std::string_view email = {}; + hpp::proto::equality_comparable_span phones; + + bool operator == (const Person&) const = default; +}; + +struct AddressBook { + hpp::proto::equality_comparable_span people; + + bool operator == (const AddressBook&) const = default; +}; +} // addressbook.pb.hpp #include "addressbook.msg.hpp" namespace tutorial { - auto pb_meta(const Person &) -> std::tuple<...> ; - auto pb_meta(const Person::PhoneNumber &) -> std::tuple<...> ; - auto pb_meta(const AddressBook &) -> std::tuple<...>; + auto pb_meta(const Person &) -> std::tuple<...> ; + auto pb_meta(const Person::PhoneNumber &) -> std::tuple<...> ; + auto pb_meta(const AddressBook &) -> std::tuple<...>; } ``` +

+
### Protobuf encoding/decoding APIs +The hpp-proto library provides convenient functions for encoding and decoding Protobuf messages in C++. The core functions are: + +- write_proto: Used to serialize a C++ structure into a Protobuf message format, typically stored in a binary buffer (e.g., std::vector). +- read_proto: Used to deserialize a Protobuf-encoded binary buffer back into the corresponding C++ structure. + +These APIs allow you to serialize and deserialize data efficiently, with overloads that return either a success/error status or an expected object (containing the result or an error). Below is the demonstration of how to use these functions in regular and non-owning modes. + + +
Regular Mode +

+ ```cpp #include "addressbook.pb.hpp" int main() { + using enum tutorial::Person::PhoneType; tutorial::AddressBook address_book{ .people = {{.name = "Alex", .id = 1, .email = "alex@email.com", - .phones = {{.number = "1111111", .type = tutorial::Person::PhoneType::MOBILE}}}, + .phones = {{.number = "1111111", + .type = MOBILE}}}, {.name = "Bob", .id = 2, .email = "bob@email.com", - .phones = {{.number = "22222222", .type = tutorial::Person::PhoneType::HOME}}} }}; + .phones = {{.number = "22222222", + .type = HOME}}} }}; std::vector buffer; @@ -360,6 +446,11 @@ int main() { return 1; } + // alternatively, use the overload returning an expected object + hpp::proto::expected, std::errc> write_result + = hpp::proto::write_proto(address_book); + assert(write_result.value() == buffer); + tutorial::AddressBook new_address_book; if (!hpp::proto::read_proto(new_address_book, buffer).ok()) { @@ -367,14 +458,81 @@ int main() { return 1; } + // alternatively, use the overload returning an expected object + hpp::proto::expected read_result + = hpp::proto::read_proto(buffer); + assert(read_result.value() == new_address_book); return 0; } ``` +

+
+ +
Non-owning Mode +

+ +```cpp +#include "addressbook.pb.hpp" +#include + +int main() { + using enum tutorial::Person::PhoneType; + using namespace std::string_view_literals; + std::pmr::monotonic_buffer_resource pool; + std::pmr::vector alex_phones{&pool}; + alex_phones.push_back({.number = "1111111"sv, .type = MOBILE}); + std::pmr::vector people{&pool}; + people.reserve(2); + people.emplace_back("Alex"sv, 1, "alex@email.com"sv, alex_phones); + std::pmr::vector bob_phones{&pool}; + bob_phones.push_back({.number = "22222222"sv, .type = HOME}); + people.emplace_back("Bob"sv, 2, "bob@email.com"sv, bob_phones); + + tutorial::AddressBook address_book; + address_book.people = people; + + std::pmr::vector buffer{&pool}; + + if (!hpp::proto::write_proto(address_book, buffer).ok()) { + std::cerr << "protobuf serialization failed\n"; + return 1; + } + + // alternatively, use the overload returning an expected object + hpp::proto::expected, std::errc> write_result + = hpp::proto::write_proto>(address_book); + assert(write_result.value() == buffer); + + tutorial::AddressBook new_address_book; + + if (!hpp::proto::read_proto(new_address_book, buffer, hpp::proto::pb_context{pool}).ok()) { + std::cerr << "protobuf deserialization failed\n"; + return 1; + } + + // alternatively, use the overload returning an expected object + hpp::proto::expected read_result + = hpp::proto::read_proto(buffer, hpp::proto::pb_context{pool}); + assert(read_result.value() == new_address_book); + + return 0; +} +``` +

+
### JSON encoding/decoding APIs -hpp-proto utilizes (glaze)[https://github.com/stephenberry/glaze] for JSON encoding/decoding. -To support the [canonical JSON encoding](https://protobuf.dev/programming-guides/proto3/#json) of protobuf messages; hpp-proto generates `*.glz.hpp` files to contain the template specializations necessary to meet the specification. The APIs for JSON encoding/decoding is similar to those of protobuf encoding/decoding. +The hpp-proto library also supports encoding and decoding Protobuf messages to and from [canonical JSON encoding](https://protobuf.dev/programming-guides/proto3/#json) using the modified (glaze)[https://github.com/stephenberry/glaze] library. This ensures compatibility with the canonical JSON encoding of Protobuf messages. The key functions are: + +- `write_json`: Used to serialize a C++ structure into a JSON string. +- `read_json`: Used to deserialize a JSON string back into the corresponding C++ structure. + +Similar to Protobuf, the JSON APIs provide overloads that return either a success/error status or an expected object. Below is a demonstration of how to use these functions for encoding and decoding in regular and non-owning modes. + + +
Regular Mode +

```cpp @@ -388,9 +546,50 @@ if (!hpp::proto::write_json(address_book, json).ok()) { return 1; } +// alternatively, use the overload returning an expected object +auto write_result = hpp::proto::write_json(address_book); +assert(write_result.value() == json); + tutorial::AddressBook new_book; if (auto e = hpp::proto::read_json(new_book, json); !e.ok()) { std::cerr << "read json error: " << e.message(json) << "\n"; return 1; } -``` \ No newline at end of file + +// alternatively, use the overload returning an expected object +auto read_result = hpp::proto::read_json(json); +assert(read_result.value() == new_address_book); +``` +

+
+
Non-owning Mode +

+ +```cpp + +#include "addressbook.glz.hpp" + +// .... +std::pmr::string json{&pool}; + +if (!hpp::proto::write_json(address_book, json).ok()) { + std::cerr << "write json error\n"; + return 1; +} + +// alternatively, use the overload returning an expected object +auto write_result = hpp::proto::write_json(address_book, hpp::proto::json_context{pool}); +assert(write_result.value() == json); + +tutorial::AddressBook new_book; +if (auto e = hpp::proto::read_json(new_book, json, hpp::proto::json_context{pool}); !e.ok()) { + std::cerr << "read json error: " << e.message(json) << "\n"; + return 1; +} + +// alternatively, use the overload returning an expected object +auto read_result = hpp::proto::read_json(json, hpp::proto::json_context{pool}); +assert(read_result.value() == new_address_book); +``` +

+
\ No newline at end of file diff --git a/docs/Code_Generation_Guide.md b/docs/Code_Generation_Guide.md index 3c78f2b..a779546 100644 --- a/docs/Code_Generation_Guide.md +++ b/docs/Code_Generation_Guide.md @@ -1,13 +1,11 @@ # Code Generation Guide -Describes exactly what C++ code the hpp-proto plugin of the protocol buffer compiler generates for any given protocol definition. - +This guide explains the C++ code generated by the hpp-proto plugin of the Protocol Buffer compiler for any given .proto schema file. ## Compiler Invocation ### Compiler Invocation from CMake -After the hpp-proto package has been added via `find_package` or `FetchContent`, the protocol buffer compiler can be invoked with -`protobuf_generate` function in the following form: +Once the hpp-proto package has been added via `find_package` or `FetchContent`, the Protocol Buffer compiler can be invoked using the `protobuf_generate` function in the following format: ```cmake protobuf_generate( @@ -19,24 +17,22 @@ protobuf_generate( [PLUGIN_OPTIONS ]) ``` - - IMPORT_DIRS: Common parent directories for the schema files. - - PROTOC_OUT_DIR: Output directory of generated source files. Defaults to CMAKE_CURRENT_BINARY_DIR. + - IMPORT_DIRS: Specifies common parent directories for schema files. + - PROTOC_OUT_DIR: Output directory for generated source files. Defaults to CMAKE_CURRENT_BINARY_DIR. - PROTOS: List of proto schema files. If omitted, then every source file ending in proto of TARGET will be used. - - PLUGIN_OPTIONS: A comma separated string that is forwarded to protoc-gen-hpp plugin to customize code generation. The customization options includes: - * `root_namespace=`: prepend a root namespace to the generated code on top of the package. - * `top_directory=`: prepend a directory to all the importing dependencies. - * `proto2_explicit_presence=`: for proto2 only, make all optional fields implicit presence except the scopes specified. This options can be specified multiple times. For example: `proto2_explicit_presence=.pkg1.msg1.field1,proto2_explicit_presence=.pkg1.msg2` specifies the `field1` of `pkg1.msg1` and all fields of `pkg1.msg2` should adopt explicit presence (i.e. use std::optional) - * `non_owning`: generate non-owning messages. - -The compiler creates several header files for each .proto file input. -The names of the output files are computed by taking the name of the .proto file and making two changes: + - PLUGIN_OPTIONS: A comma-separated string forwarded to the protoc-gen-hpp plugin to customize code generation. Options include: + * `root_namespace=`: Prepends a root namespace to the generated code, in addition to the package namespace. + * `top_directory=`: Prepends a directory to all import dependencies. + * `proto2_explicit_presence=`: For Proto2 only, makes optional fields implicitly present except for specified scopes. This option can be specified multiple times. For example: `proto2_explicit_presence=.pkg1.msg1.field1,proto2_explicit_presence=.pkg1.msg2` specifies the `field1` of `pkg1.msg1` and all fields of `pkg1.msg2` should adopt explicit presence (i.e. use std::optional) + * `non_owning`: Generates non-owning messages. -The extension (.proto) is replaced with `.msg.hpp`, `.pb.hpp`, `.glz.hpp` and `.desc.hpp` for each header files. -The proto path (specified with the --proto_path= or -I command-line flag) is replaced with the output path (specified with the --hpp_out flag). +The compiler generates several header files for each .proto file, transforming the filename and extension as follows: +- The `.proto` extension is replaced with `.msg.hpp`, `.pb.hpp`, `.glz.hpp` and `.desc.hpp` for different header file types. +- The proto path (specified with --proto_path= or -I flags) is replaced with the output path (specified with the --hpp_out flag). -Example: -``` +Example CMake configuration: +```cmake add_library(non_owning_unittest_proto3_proto_lib INTERFACE) target_include_directories(non_owning_unittest_proto3_proto_lib INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) protobuf_generate( @@ -49,7 +45,7 @@ protobuf_generate( ``` ### Compiler Invocation from command line -The compiler plugin `protoc-gen-hpp` is in your `$PATH` for the protocol compiler `protoc` to find it. +You can invoke the hpp-proto plugin from the command line with the following command: ``` protoc --hpp_out= [--hpp_opt=] [--proto_path=] @@ -58,32 +54,30 @@ protoc --hpp_out= [--hpp_opt=] [--proto_path=] < ## Packages -If a .proto file contains a package declaration, the entire contents of the file will be placed in a corresponding C++ namespace. For example, given the package declaration: +If a .proto file contains a package declaration, the entire contents are placed in a corresponding C++ namespace. For example: ``` package foo.bar; ``` -All declarations in the file will reside in the `foo::bar` namespace. +All declarations in the file will reside in the `foo::bar` namespace. -In certain cases, you may want to use `hpp-proto` and the official google Protobuf in the same program. You can use the `root_namespace` option when invoking protocol buffer compiler to customize the top level namespace of the generated files. +If you need to use both `hpp-proto` and the official Google Protobuf library in the same program, you can use the root_namespace option to customize the top-level namespace in the generated files. For example: -For example, invoking the compiler as follows: ``` -protoc --proto_path=src --plugin=protoc-gen-hpp=$HOME/local/bin/protoc-gen-hpp --hpp_out build/gen --hpp_opts=root_namespace=baz src/foo.proto +protoc --proto_path=src --hpp_out build/gen --hpp_opt=root_namespace=baz src/foo.proto ``` -All declarations in the file will reside in the `baz::foo::bar` namespace. +This command will place all declarations in the `baz::foo::bar` namespace. ## Messages -Given a simple message declaration: - +Given a simple message declaration like: ``` message Foo {} ``` -The protocol buffer compiler generates a struct called Foo. +The protocol buffer compiler generates a struct called `Foo`. ## Fields -In addition to the methods described in the previous section, the protocol buffer compiler generates a set of member variables for each field defined within the message in the .proto file. +For each field defined in a .proto message, the compiler generates corresponding member variables in the C++ `struct`. ### Implicit Presence Fields (proto3) @@ -97,49 +91,82 @@ message Foo { ``` The compiler will generate the following `struct`: -``` + +
Regular Mode +

+ +```cpp struct Foo { int32_t f1; std::string f2; std::vector f3; }; ``` +

+
+
Non-owning Mode +

+```cpp +struct Foo { + int32_t f1; + std::string f2; + hpp::proto::equality_comparable_span f3; +}; +``` +

+
+ +The `hpp::proto::equality_comparable_span` is a template class which inherits `std::span` and adds an additional equality comparison operator. ### Explicit Presence Fields (proto2) -For either of these field definitions: +For a Proto2 message with optional fields: ``` syntax = "proto2"; message Foo { optional int32 f1 = 1; optional string f2 = 2; optional bytes f3 = 3; + optional int64 f4 = 4 [default = 100]; } ``` The compiler will generate the following `struct`: -``` +
Regular Mode +

+ +```cpp struct Foo { hpp::proto::optional f1; hpp::proto::optional f2; hpp::proto::optional> f3; + hpp::proto::optional f4; }; ``` +

+
+
Non-owning Mode +

-`hpp::proto::optional` has all the members functions of `std::optional` with the addition of `value_or_default()` which returns the default value when the contained value is not present. - -Furthermore, the specialization of `hpp::proto::optional` deletes the type conversion operator to bool to avoid the confusion between value not present versus value is false. +```cpp +struct Foo { + hpp::proto::optional f1; + hpp::proto::optional f2; + hpp::proto::optional> f3; + hpp::proto::optional f4; +}; +``` +

+
-In most case, it may not be necessary to differentiate the value not present and default value cases for all optional fields. The `proto2_explicit_presence` plugin option can be used to generate code that only apply `hpp::proto::optional` to a specific set of fields. +The `hpp::proto::optional` type adds a `value_or_default()` method, which returns the default value if the field is not present. The specialization for `hpp::proto::optional` deletes type conversion to `bool` to avoid ambiguity between a missing value and false. +When explicit presence is required only for some fields, the `proto2_explicit_presence` option allows selective use of `hpp::proto::optional`. ### Optional Embedded Message Fields (proto2 and proto3) -Given the message type: -```protobuf -message Bar {} -``` -For any of these field definitions: + +Given `Bar` is a message, any of these field definitions like: ``` //proto2 @@ -148,13 +175,19 @@ optional Bar foo = 1; //proto3 Bar foo = 1; ``` -The compiler will generate `std::optional foo;` member variable. + +The compiler will generate: +``` +std::optional foo; +``` ### Repeated Fields -Given a repeated field of type `T` in a message, the compiler will generate `std::vector` for the corresponding field. +For repeated fields, the compiler generates `std::vector` in regular mode and +`hpp::proto::equality_comparable_span` in non-owning mode. -### Oneof fields +### Oneof fields +For oneof fields: ```protobuf message TestOneof { oneof foo { @@ -169,7 +202,8 @@ message TestOneof { } ``` -```C++ +The compiler maps the oneof field to `std::variant`, with the first alternative being `std::monostate`: +```cpp struct TestOneof { struct NestedMessage { double required_double = {}; @@ -192,59 +226,95 @@ struct TestOneof { ### Map fields - +For `map` fields: ```proto message TestMap { map map1 = 1; } ``` +The compiler generates `hpp::proto::flat_map` for regular mode and with `hpp::proto::equality_comparable_span>` for non-owning mode. In the non-owning mode, +it is up to user to make sure there's no duplicated keys in the fields; the library does not enforce the no duplicated keys rule. + + +
Regular Mode +

-```C++ +```cpp struct TestMap { hpp::proto::flat_map map1; - bool operator == (const TestMap&) const = default; }; ``` +

+
+
Non-owning Mode +

+```cpp +struct TestMap { + hpp::proto::equality_comparable_span> map1; + bool operator == (const TestMap&) const = default; +}; +``` +

### Any Type +For google.protobuf.Any fields: ```protobuf message TestAny { - int32 int32_value = 1; google.protobuf.Any any_value = 2; - repeated google.protobuf.Any repeated_any_value = 3; - string text = 4; } ``` +The compiler generates `::google::protobuf::Any`. -```C++ +```cpp struct TestAny { - int32_t int32_value = {}; std::optional<::google::protobuf::Any> any_value; - std::vector<::google::protobuf::Any> repeated_any_value; - std::string text = {}; - bool operator == (const TestAny&) const = default; }; ``` -```C++ +To read or write Any fields, use `hpp::proto::unpack_any` and `hpp::proto::pack_any`. +
Regular Mode +

+ +```cpp TestAny message; - google::protobuf::FieldMask fm{.paths = {"/usr/share", "/usr/local/share"}}; - expect(hpp::proto::pack_any(message.any_value.emplace(), fm).ok()); + using namespace std::string_literals; + google::protobuf::FieldMask fm{.paths = {"/usr/share"s, "/usr/local/share"s}}; + assert(hpp::proto::pack_any(message.any_value.emplace(), fm).ok()); std::vector buf; - expect(hpp::proto::write_proto(message, buf).ok()); + assert(hpp::proto::write_proto(message, buf).ok()); TestAny message2; - expect(hpp::proto::read_proto(message2, buf).ok()); + assert(hpp::proto::read_proto(message2, buf).ok()); google::protobuf::FieldMask fm2; - expect(hpp::proto::unpack_any(message2.any_value.value(), fm2).ok()); - expect(fm == fm2); + assert(hpp::proto::unpack_any(message2.any_value.value(), fm2).ok()); ``` +

+
+
Non-owning Mode +

-## Non-owning Mode +```cpp + TestAny message; + using namespace std::string_view_literals; + std::array paths = {"/usr/share"sw, "/usr/local/share"sw}; + google::protobuf::FieldMask fm; + fm.paths = paths; + std::pmr::monotonic_buffer_resource pool; + hpp::proto::pb_context ctx{pool}; + + assert(hpp::proto::pack_any(message.any_value.emplace(), fm, ctx).ok()); -Non-owning Mode is similar to the arena allocation in google protobuf implementation that helps you optimize your memory usage and improve performance. Generating code in non-owning mode will make all owning containers in the generated regular mode code like std::string or std::vector to use std::string_view and std::span instead. + std::pmr::vector buffer{&pool}; + assert(hpp::proto::write_proto(message, buffer).ok()); + TestAny message2; + assert(hpp::proto::read_proto(message2, buf, ctx).ok()); + google::protobuf::FieldMask fm2; + assert(hpp::proto::unpack_any(message2.any_value.value(), fm2, ctx).ok()); +``` +

+
\ No newline at end of file From a857deb4778ce4ec0fe92e19d665c5a1170b9aca Mon Sep 17 00:00:00 2001 From: Clang Robot Date: Sun, 20 Oct 2024 23:18:43 +0000 Subject: [PATCH 3/3] :art: Committing clang-format changes --- tutorial/non_owning/non_owning_tutorial_proto3.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial/non_owning/non_owning_tutorial_proto3.cpp b/tutorial/non_owning/non_owning_tutorial_proto3.cpp index ad03c38..22135bc 100644 --- a/tutorial/non_owning/non_owning_tutorial_proto3.cpp +++ b/tutorial/non_owning/non_owning_tutorial_proto3.cpp @@ -89,7 +89,8 @@ int main() { "https://en.wikipedia.org/wiki/1989_Tiananmen_Square_protests_and_massacre"); } - auto write_json_result = hpp::proto::write_json(address_book, hpp::proto::json_context{pool}); + auto write_json_result = + hpp::proto::write_json(address_book, hpp::proto::json_context{pool}); expect(write_json_result.has_value()); auto read_json_result = hpp::proto::read_json(write_json_result.value(), hpp::proto::json_context{pool});