Table of Contents
C++ Guide
The C++ runtime targets C++20, uses synchronous operations, and reports failures through explicit Result values. Generated bindings use protobuf C++ full or lite messages. Transport, framing, cancellation, limits, and reconnection reuse the byte-oriented trevrpc-c runtime; C++ does not generate or link protobuf-c.
Build And Generate
Build the runtime, plugin, tests, and examples from the repository root:
cmake -S trevrpc-cpp -B build/trevrpc-cpp
cmake --build build/trevrpc-cpp
ctest --test-dir build/trevrpc-cpp --output-on-failure
Generate protobuf messages and TrevRPC service bindings from the same schema:
protoc -I proto \
--cpp_out=gen/cpp \
--trevrpc-cpp_out=gen/cpp \
proto/example/greeter.proto
The plugin emits .trevrpc.hpp and .trevrpc.cpp beside protobuf's .pb.h and .pb.cc. Link generated targets with trevrpc::cpp and either protobuf::libprotobuf or protobuf::libprotobuf-lite. Lite schemas use option optimize_for = LITE_RUNTIME;.
Installed CMake packages provide the trevrpc_cpp_generate helper. Pass PROTOBUF_TARGET protobuf::libprotobuf-lite when the schema selects lite runtime; otherwise it defaults to protobuf::libprotobuf. The plugin options are runtime_include, header_suffix, and source_suffix.
Generated API
For a protobuf service named Greeter, the plugin generates:
GreeterService, a synchronous abstract server interface.GreeterClient, a typed client holding a sharedChannel.RegisterGreeter, which registers and retains a shared service implementation.
Generated clients expose all four RPC shapes. Unary calls return Result<UnaryResponse<Response>>. Streaming methods return typed ServerStreamingCall, ClientStreamingCall, or BidirectionalStreamingCall objects.
Each stream receive() returns a StreamEvent<Response>. A message event owns one decoded protobuf response. A terminal event owns the final Status and trailing metadata. Client and bidirectional calls expose send(), finish_send(), receive(), cancel(), and close().
Result And Errors
Every fallible operation returns Result<T> or Result<void>:
hello::v1::GreeterClient client(channel);
hello::v1::HelloRequest request;
request.set_name("TrevRPC");
auto result = client.SayHello(request);
if (!result) {
std::cerr << result.error().message() << '\n';
return;
}
std::cout << result.value().message.message() << '\n';
Error::kind() distinguishes runtime, RPC-status, and protobuf failures. RPC errors retain their Status, including code, message, and metadata. Malformed requests become InvalidArgument; malformed responses remain protobuf errors. Unexpected C++ exceptions are contained at the C callback boundary and returned as Internal.
Service Methods
Generated service signatures follow the RPC shape:
| Shape | Handler result |
|---|---|
| Unary | Result<Response>(CallContext, Request) |
| Server streaming | Status(CallContext, Request, ServerWriter<Response>) |
| Client streaming | Result<Response>(CallContext, ServerReader<Request>) |
| Bidirectional streaming | Status(CallContext, ServerReaderWriter<Request, Response>) |
Register a shared implementation before serving:
auto listening = trevrpc::Server::listen(config);
trevrpc::Server server = std::move(listening).value();
auto registered = hello::v1::RegisterGreeter(
server, std::make_shared<GreeterImplementation>());
if (!registered) {
// Handle duplicate routes or another registration error.
}
auto served = server.serve();
Channel Lifecycle
Create one long-lived channel per destination and share it across generated clients:
auto connected = trevrpc::Channel::connect("127.0.0.1", 50051, config, 5s);
auto channel = std::move(connected).value();
hello::v1::GreeterClient client(channel);
Each RPC remains pinned to the ready connection generation on which it starts. TrevRPC never retries, replays, resumes, or moves an RPC. Reconnection only enables future calls. Stop starting calls before Channel::close(); destruction performs the required C close-then-release sequence.
Streams are move-only owners. Finish the request side during normal completion, consume the terminal event, and cancel or close abandoned calls. Independent send and receive threads may be used for bidirectional protocols, but destruction must not race new stream or channel operations.
See Protobuf and Code Generation for repository-wide generation and Connection Lifecycle for the inherited transport contract.