This document explains how to set up, build, and understand the internals of the C++ protocol library. For a high-level overview of structural subtyping, library design principles, and code examples, please refer to the README.
Before building, ensure you have CMake 3.25 or
later, a GCC with C++26 reflection (P2996) support, and
uv installed. The
reflection compiler is either the GCC trunk snapshot from
jwakely.github.io/pkg-gcc-latest
or Ubuntu 26.04's gcc-16 package. The project relies on uv to manage
Python dependencies and execute build scripts.
The project supports both CMake and Bazel build systems.
- CMake: To build the project and run tests with CMake, execute:
./scripts/cmake.shFor more detailed CMake options, run ./scripts/cmake.sh --help.
- Bazel: To build the project and run tests with Bazel, execute:
./scripts/bazel.shBoth scripts select a reflection-capable compiler and pass it to their
underlying build system; a bare cmake/bazel invocation skips that and
fails on stock GCC.
Pull requests run the workflows in .github/workflows. The following checks
are required for merging to main: GCC trunk Release, GCC trunk Debug,
GCC-16 Release, GCC-16 Debug, asan, tsan, uv-lock, pre-commit.
On pull requests that touch no C++, CMake, or build-script sources, the build and sanitizer jobs are skipped by a change-detection job, which counts as passing.
The "Clang Tidy" workflow runs clang-tidy on every translation unit and fails
on any finding (WarningsAsErrors: '*' in .clang-tidy). It runs nightly, on
pushes to main, and on pull requests that touch C++ or CMake sources; it is
not a required status check.
Released clang-tidy versions cannot parse the reflection sources, so the
workflow uses a clang-tidy built from the
clang-p2996 fork, cached between
runs and rebuilt when .github/clang-p2996-commit changes. To reproduce
locally, build the fork's clang, clang-tidy, and libc++, then:
XYZ_PROTOCOL_CLANG_P2996_DIRECTORY=<toolchain root> ./scripts/cmake.sh --debug --clang-tidyThe same run is available as an opt-in pre-commit hook. It is a full build, so it is not part of the default commit-time hooks:
uv run pre-commit run --hook-stage manual clang-tidyThe fork is based on LLVM 21; the check set differs between clang-tidy
releases, so a different version may report different findings. CMake warns at
configure time when the version it found does not match
ClangTidy_EXPECTED_MAJOR_VERSION in cmake/modules/FindClangTidy.cmake.
Fix genuine findings. Suppress false positives at the site with a NOLINT
comment that names the check and gives a reason, or, for a check that does not
apply to a whole directory, in that directory's .clang-tidy (see
tutorials/.clang-tidy). Checks disabled for the whole repository are listed
with their rationale at the top of .clang-tidy.
Test coverage is measured in two halves, because much of protocol.hh is
consteval code that executes inside the compiler where runtime coverage
instrumentation cannot see it.
Runtime coverage uses GCC's gcov:
./scripts/cmake.sh test --coverageThis builds the Debug preset with --coverage, runs the tests, prints a
gcovr summary and writes an LCOV trace to <build-dir>/coverage.info. gcov
marks consteval code as non-executable, so it neither counts towards nor
against these numbers. The usual workaround for constexpr code - running
every test at both compile time and runtime so that runtime instrumentation
observes it - does not work here: immediate functions cannot execute at
runtime, and calling a P2996 metafunction makes the caller immediate too.
Consteval coverage is instead measured by compile-time trap probing:
./scripts/cmake.sh build # provides the googletest headers
./scripts/consteval_coverage.shThe tool instruments a copy of protocol.hh with trap calls at every block
entry and return/throw statement in consteval code, then recompiles the
protocol-instantiating test translation units once per trap with
-fsyntax-only, arming one trap at a time; a compile failure proves the
test suite evaluated that line, because a constant evaluation has no other
observable side effect. The design and its limitations are documented in
scripts/consteval_coverage.py. Pass --lcov-output <path> for an LCOV
trace that merges with the runtime one.
The "Coverage" workflow runs both measurements and uploads them to Codecov
under the runtime and consteval flags; it is not a required status
check.
Traditional nominal subtyping requires a type to explicitly inherit from another. Structural subtyping, in contrast, considers two types equivalent if they have the same structure, typically meaning they support the same set of operations. In this project, a type implements a protocol if it provides all the member functions defined by that protocol with compatible signatures.
Type erasure is a technique that hides the specific type of an object, allowing
it to be manipulated through a common interface. The protocol wrapper in this
library uses type erasure internally. It holds any object that structurally
conforms to the defined interface without requiring that object to inherit from
a common base class, thus enabling polymorphism without traditional inheritance
constraints.
Interfaces for protocol and protocol_view are plain structs or classes.
Their public, non-virtual, non-template member functions define the protocol;
protocol.hh inspects them with C++26 reflection at compile time.
-
Member Functions: overloaded member functions (distinguished by parameter types or arity),
operator()including overloaded call operators, const and non-const overloads of the same member,noexceptmember functions, and static member functions on the conforming type (a static candidate has no object parameter, so it can satisfy any const or reference qualification of the interface member). Static member functions declared on the interface itself are ignored. -
Limitations: No operators other than
operator()are supported. Member function templates are not matched, since only non-template functions are considered as candidates. Conformance checking accounts for an interface member's lvalue/rvalue reference qualifier, but the generated call wrapper does not itself apply the qualifier. There is no conversion between aprotocol/protocol_viewof one interface and another. Conformance checking is O(N*M) in the number of interface and candidate member functions. -
Guidance: Developers should refer to
protocol_test.cc,forwarding_test.cc,allocator_tests.ccandtutorials/reflection.ccfor examples of supported interface patterns.
Examples of how to use the protocol library can be found within the test
suite.
protocol_test.cc: This file contains tests that demonstrate the library's intended usage. It shows how to define an interface and then instantiate and use the wrapper with concrete types. Developers can examine this file for practical examples.
This library is an active proof of concept and is subject to change.
-
Not for Production: It is not set up for use in other projects.
-
Limitations: It has unknown limitations and may experience breaking changes.
-
Development: Use this library for understanding its concepts and contributing to its development. Avoid using it in production code.
The repository includes a Docker-based sandbox script for AI coding assistants. It mounts the project into a container with all build dependencies pre-installed, providing an isolated environment for AI-assisted development.
./scripts/agentic-sandbox.sh <agent> [options]where <agent> is either claude or gemini.
| Flag | Description |
|---|---|
--rebuild-docker |
Rebuild the Docker image before starting. |
--update |
Update the agent CLI to the latest version before running. |
-v, --verbose |
Enable verbose logging. |
Install pre-commit hooks into your local repository:
uv run pre-commit installRun all hooks against every file:
uv run pre-commit run --all-files- Issues: All issues, bugs, and feature requests should be tracked on the project's GitHub repository: https://github.com/jbcoe/cc-protocol/issues
Last updated: August 30, 2026