Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
Language: Cpp
# No `Language: Cpp` key: clang-format's language guesser classifies
# protocol_reflection.h (C++26 `^^` reflection syntax) as Objective-C, and a
# Cpp-only configuration makes it error out on that file. A single
# language-agnostic document applies to every language clang-format detects.
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
Expand Down Expand Up @@ -153,4 +155,3 @@ StatementMacros:
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
...
26 changes: 24 additions & 2 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
configuration: ["Release", "Debug"]
configuration: ["release", "debug"]
settings:
- {
name: "Ubuntu GCC-11",
Expand Down Expand Up @@ -211,6 +211,20 @@ jobs:
std: 20,
},
}
- {
name: "Ubuntu GCC-16 (C++26 Reflection)",
os: ubuntu-24.04,
compiler:
{
type: GCC16,
version: 16,
cc: "gcc-16",
cxx: "g++-16",
std: 26,
},
lib: "libstdc++16",
implementation: "reflection",
}
steps:
- uses: actions/checkout@v4
- uses: seanmiddleditch/gha-setup-ninja@master
Expand All @@ -231,9 +245,17 @@ jobs:
with:
version: ${{ matrix.settings.compiler.version }}
platform: x64
- name: Install GCC 16 from ubuntu-toolchain-r PPA
if: matrix.settings.compiler.type == 'GCC16'
run: |
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install -y --no-install-recommends g++-16 gcc-16
echo "CC=gcc-16" >> "$GITHUB_ENV"
echo "CXX=g++-16" >> "$GITHUB_ENV"
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv sync
- name: Run CMake
run: ./scripts/cmake.sh --${{ matrix.configuration == 'Debug' && 'debug' || 'release' }}
run: ./scripts/cmake.sh --${{ matrix.configuration }} --implementation=${{ matrix.settings.implementation || 'Python' }}
88 changes: 88 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ option(ENABLE_UBSAN "Enable Undefined Behaviour Sanitizer" OFF)
option(ENABLE_TSAN "Enable Thread Sanitizer" OFF)
option(ENABLE_MSAN "Enable Memory Sanitizer" OFF)

option(
XYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND
"Also build and test the C++26-reflection code-generation \
backend (protocol_reflection.h). Requires a compiler with P2996 reflection \
support (GCC 16+ with -freflection). The default Python code-generation \
path is unaffected."
OFF)

if(XYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND)
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS "-std=c++26 -freflection")
check_cxx_source_compiles(
"
#include <meta>
constexpr std::meta::info reflection_of_int = ^^int;
int main() {}
"
XYZ_PROTOCOL_REFLECTION_SUPPORTED)
unset(CMAKE_REQUIRED_FLAGS)
if(NOT XYZ_PROTOCOL_REFLECTION_SUPPORTED)
message(
FATAL_ERROR
"XYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND is ON but the compiler "
"(${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}) does not "
"accept '-std=c++26 -freflection'. C++26 reflection currently "
"requires GCC 16 or newer; configure with e.g. "
"CXX=g++-16 CC=gcc-16 and a separate build directory (-B).")
endif()
endif()

if(ENABLE_ASAN OR ENABLE_UBSAN OR ENABLE_TSAN OR ENABLE_MSAN)
set(ENABLE_SANITIZERS ON)
endif()
Expand Down Expand Up @@ -148,6 +178,47 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
target_include_directories(protocol_test
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

if(XYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND)
# protocol_test.cc includes the per-interface headers directly (no
# Python-generated "generated/protocol_<Name>.h") when
# XYZ_PROTOCOL_ENABLE_REFLECTION is defined: protocol_reflection.h
# synthesizes the same machinery from the plain interface struct at
# compile time, so no code generation step or shim headers are needed.
xyz_add_test(
NAME
protocol_test_reflection
VERSION
26
LINK_LIBRARIES
protocol
FILES
protocol_test.cc
protocol_reflection_detail/naming_test.cc
protocol_reflection_detail/members_test.cc
protocol_reflection_detail/types_test.cc
protocol_reflection_detail/thunk_test.cc
protocol_reflection_detail/conformance_test.cc
protocol_reflection_detail/vtable_layout_test.cc
protocol_reflection.h
protocol_reflection_detail/naming.h
protocol_reflection_detail/members.h
protocol_reflection_detail/types.h
protocol_reflection_detail/thunk.h
protocol_reflection_detail/conformance.h
protocol_reflection_detail/vtable_layout.h
protocol_reflection_detail/operator_forwarders.h
interface_A.h
interface_A_Subset.h
interface_B.h
interface_C.h
interface_D.h)
target_include_directories(protocol_test_reflection
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(protocol_test_reflection PRIVATE -freflection)
target_compile_definitions(protocol_test_reflection
PRIVATE XYZ_PROTOCOL_ENABLE_REFLECTION)
endif()

add_executable(protocol_benchmark protocol_benchmark.cc)
target_link_libraries(protocol_benchmark PRIVATE protocol benchmark::benchmark)
add_dependencies(protocol_benchmark generate_protocols)
Expand All @@ -170,6 +241,23 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
--flags=-I${CMAKE_CURRENT_BINARY_DIR}
--flags=-I${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

if(XYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND)
# No generated-header include path is needed here (unlike
# concept_errors_test above): the reflection backend synthesizes its
# machinery from a plain struct at compile time.
add_test(
NAME concept_errors_test_reflection
COMMAND
${Python3_EXECUTABLE} -m pytest
${CMAKE_CURRENT_SOURCE_DIR}/scripts/test_concept_errors_reflection.py
--compiler=${CMAKE_CXX_COMPILER}
--flags=-std=c++26
--flags=-freflection
--flags=-DXYZ_PROTOCOL_ENABLE_REFLECTION
--flags=-I${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()
endif()
endif(${BUILD_TESTING})

Expand Down
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ The `xyz_generate_protocol` CMake macro automates code generation and supports e
This macro ensures that the Python script runs during the CMake configuration
or build phase to generate the necessary C++ source files for the protocol.

### C++26 Reflection Backend

An opt-in second backend, `protocol_reflection.h`, generates the same
machinery inside the compiler using C++26 reflection instead of a build step.
It requires GCC 16+ (`-freflection`) and is enabled with a CMake option:

```bash
CXX=g++-16 CC=gcc-16 ./scripts/cmake.sh --release \
-DXYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND=ON -B build-reflection
```

This builds an additional test binary, `protocol_test_reflection`, running
the same tests against the reflection backend. See `implementation-notes.md`
(section 5) for the backend's design and known limitations.

## Performance and Benchmarks

You can measure the performance of member function dispatch, copying, and moving by running the `protocol_benchmark` target via the wrapper script:
Expand Down Expand Up @@ -144,6 +159,21 @@ This library is an active proof of concept and is subject to change.
- Development: Use this library for understanding its concepts and
contributing to its development. Avoid using it in production code.

## Interactive Docker Shell

To launch an interactive bash shell in the pre-configured Docker container (which includes GCC 16, Python, CMake, and `uv`) without running an AI agent, use:

```bash
./scripts/docker-shell.sh [--rebuild-docker]
```

Inside the shell, you can experiment with the C++26 reflection backend directly:

```bash
CXX=g++-16 CC=gcc-16 ./scripts/cmake.sh --release \
-DXYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND=ON -B build-reflection
```

## AI Coding Sandboxes

The repository includes a Docker-based sandbox script for AI coding
Expand Down
95 changes: 44 additions & 51 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,44 @@
# Gemini Project Context: protocol

This file provides project-specific mandates and conventions that override
general defaults for this repository.

## Engineering Standards

- **C++ Specification:** Target C++20/26. Prioritize value semantics, type
erasure, and allocator-aware designs consistent with P3019
(`std::polymorphic`).
- **Naming Conventions:** NEVER use abbreviations in public or internal variable
names. Use descriptive names like `XYZ_GENERATE_MANUAL_VTABLE` instead of
`XYZ_GEN_MAN_VT`.
- **Stability:** Ensure that generated symbols (e.g., vtable entry names) remain
deterministic and stable between runs by using MD5 hashing of function
signatures.
- **WG21 Style:** `DRAFT.md` must adhere to ISO C++ standardization proposal
norms <https://www.open-std.org/jtc1/sc22/wg21/docs/papers>
- **Paper Format:** We use pure Markdown, no YAML frontmatter, and no HTML blocks.

## Workflow Mandates

- **Tooling:** Always use `uv` for Python dependency management (`uv run ...`).
- **Build & Test:** Use `scripts/cmake.sh` for all build and test operations.
The `scripts/cmake.sh` entrypoint supports `--debug`, `--release`,
`--asan`, `--ubsan`, `--tsan`, and `--msan`.
- **Compiler Preferences:** Prefer Clang 19+ for sanitizer-based verification
and CI, as it provides superior support for MSAN and TSAN compared to
older GCC versions.
- **Verification:** All changes must be verified using the `scripts/cmake.sh`
script to build and test the implementation.
- **Sanitizer Verification:** When modifying memory-sensitive or concurrent
code, verify changes locally using at least one sanitizer (e.g.,
`./scripts/cmake.sh --asan` or `--tsan`). Note that ASAN, TSAN, and MSAN
are mutually exclusive.
- **Post-Change Checks:** Tests and pre-commit checks MUST be run after any
modifications to the codebase.

## Git Usage

- **Source Control:** This repository uses git.
- **History Integrity:** NEVER use git commands that affect the git history.
- **Commit & Branching:** Never commit changes, create, or delete branches.
- **Human Intervention:** If git commands must be run, you MUST ask for human
intervention.

## Critical Paths

- Generation Script: `scripts/generate_protocol.py`
- Proposal Draft: `DRAFT.md`
- Build Entrypoint: `scripts/cmake.sh`
# protocol: project conventions

Project-specific mandates that override defaults for this repo.

## Engineering standards

- C++20/26. Prioritize value semantics, type erasure, allocator-aware design
(P3019 `std::polymorphic`).
- No abbreviations in variable names (`XYZ_GENERATE_MANUAL_VTABLE`, not
`XYZ_GEN_MAN_VT`).
- Generated symbols (e.g. vtable entry names) must be deterministic,
stable, and overload-disambiguating. The Python backend mangles by
hashing the function signature (MD5); the reflection backend instead
escapes the signature string byte-for-byte into a valid identifier
(`identifier_safe_string`), which is injective by construction rather than
probabilistically collision-resistant.
- `DRAFT.md` follows WG21 proposal norms
(<https://www.open-std.org/jtc1/sc22/wg21/docs/papers>). Pure Markdown
only — no YAML frontmatter, no HTML.

## Workflow

- Python deps: always `uv run ...`.
- Build/test only via `scripts/cmake.sh`: `--debug`/`--release`,
`--asan`/`--ubsan`/`--tsan`/`--msan` (mutually exclusive),
`--implementation=Python|reflection`. `reflection` needs a P2996 compiler
(e.g. `CXX=g++-16 CC=gcc-16`) and hard-fails rather than falling back.
- Prefer Clang 19+ for sanitizers/CI (better MSAN/TSAN than GCC).
- While iterating, a targeted compile/test is enough. Before calling a
change done, run the full suite via `scripts/cmake.sh` plus pre-commit
checks. If the change touches allocators or manual object lifetime
(clone/move/destroy, pointer casts), also run `--asan --ubsan`. If it
touches the vtable registry's shared cache/mutex (`get_mapped_vtable`),
also run `--tsan`.

## Git

- Never commit, branch, or run history-altering git commands. Ask the human
if one is needed.

## Critical paths

- Generator: `scripts/generate_protocol.py` · Proposal: `DRAFT.md` · Build:
`scripts/cmake.sh`
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,36 @@

We propose the addition of two class templates, `protocol<T, A>` and
`protocol_view<T>`, to the C++ Standard Library. Both classes support
structural-subtyping, `protocol` is owning, `protocol_view` is non-owning.
structural subtyping: `protocol` is owning (allocator-aware), and
`protocol_view` is non-owning.

See [DRAFT.md](DRAFT.md) for more details on design.
See [DRAFT.md](DRAFT.md) for complete details on the proposal design and library specifications.

This repository contains both the ISO C++ proposal to add these new library
types and a reference implementation. The reference implementation is currently
reliant on a Python code-generation step as C++26 reflection is missing some of
the features needed to generate code needed by these types at compile time.
## Implementation Architecture & Backends

This repository contains both the ISO C++ proposal paper and a reference implementation supporting two code-generation backends:

1. Python Build-Step Backend (Default): Uses `libclang` (`py_cppmodel`) and Jinja2 templates ([scripts/protocol.j2](scripts/protocol.j2)) to parse C++ interface headers and generate static vtable specializations at build time. Supported on all target C++20 compilers (GCC 11-14, Clang 17-21, MSVC, Apple Clang).
2. C++26 Reflection Backend (Opt-In): Uses C++26 reflection ([P2996R13]) via [protocol_reflection.h](protocol_reflection.h) to synthesize vtables, forwarding wrappers, and concepts entirely inside the compiler at compile time without any external Python generation step. Requires GCC 16+ with `-freflection`.

Both backends share the same public API in [protocol.h](protocol.h), support narrowing conversions, and pass the identical test suite. Full removal of the Python code-generation step remains future work as compiler reflection implementations mature.

For deep architectural details, see [implementation-notes.md](implementation-notes.md).

## Quick Start & Building

Ensure you have [CMake](https://cmake.org/), a modern C++ compiler, and [uv](https://docs.astral.sh/uv/) installed.

```bash
# Build and run tests with default Python backend
./scripts/cmake.sh

# Build and run tests with the C++26 Reflection backend (requires GCC 16+)
CXX=g++-16 CC=gcc-16 ./scripts/cmake.sh --release \
-DXYZ_PROTOCOL_ENABLE_REFLECTION_BACKEND=ON -B build-reflection
```

For complete build options, benchmarking, and development practices, see [CONTRIBUTING.md](CONTRIBUTING.md).

## Standardization

Expand All @@ -22,14 +44,6 @@ The paper [P4148R2](https://wg21.link/P4148R2.pdf) (derived from
working group in Brno on June 11th 2026. The authors have been encouraged to
continue work.

## Contributing and Development

For build instructions, testing, contributing guidelines, and a deeper look into
the code generation architecture, see [CONTRIBUTING.md](CONTRIBUTING.md).

## GitHub codespaces
## GitHub Codespaces

Press `.` or visit [https://github.dev/jbcoe/cc-protocol] to open the project in
an instant, cloud-based, development environment. We have defined a
[devcontainer](.devcontainer/devcontainer.json) that will automatically install
the dependencies required to build and test the project.
Press `.` or visit [https://github.dev/jbcoe/cc-protocol](https://github.dev/jbcoe/cc-protocol) to open the project in an instant, cloud-based development environment. We provide a [devcontainer](.devcontainer/devcontainer.json) pre-configured with all required dependencies, including GCC 16 for C++26 reflection experiments.
2 changes: 1 addition & 1 deletion cmake/xyz_add_test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function(xyz_add_test)
if(NOT XYZ_VERSION)
set(XYZ_VERSION 20)
else()
set(VALID_TARGET_VERSIONS 11 14 17 20 23)
set(VALID_TARGET_VERSIONS 11 14 17 20 23 26)
list(FIND VALID_TARGET_VERSIONS ${XYZ_VERSION} index)
if(index EQUAL -1)
message(FATAL_ERROR "TYPE must be one of <${VALID_TARGET_VERSIONS}>")
Expand Down
10 changes: 10 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/nul
&& apt-get update && apt-get install -y --no-install-recommends cmake \
&& rm -rf /var/lib/apt/lists/*

# Install GCC 16 (experimental C++26 reflection support via -std=c++26
# -freflection, see https://gcc.gnu.org/gcc-16/changes.html) from the Ubuntu
# Toolchain Test PPA, since Ubuntu 24.04's own repos only go up to GCC 14.
# This is additive: the default g++ (13.3.0) installed above remains the
# toolchain used for the default build; g++-16/gcc-16 are opt-in, invoked
# explicitly (e.g. via CMAKE_CXX_COMPILER=g++-16) for the reflection backend.
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test \
&& apt-get update && apt-get install -y --no-install-recommends g++-16 gcc-16 \
&& rm -rf /var/lib/apt/lists/*

# Install bazelisk.
RUN ARCH=$(dpkg --print-architecture) && \
wget https://github.com/bazelbuild/bazelisk/releases/download/v1.25.0/bazelisk-linux-${ARCH} -O /usr/local/bin/bazelisk \
Expand Down
Loading
Loading