|
4 | 4 | [](https://en.wikipedia.org/wiki/C%2B%2B20) |
5 | 5 | [](https://opensource.org/licenses/MIT) |
6 | 6 |
|
7 | | -An experimental C++ library and code generation tool for static structural |
8 | | -subtyping (duck typing) with value semantics. |
| 7 | +We propose the addition of two class templates, `protocol<T, A>` and |
| 8 | +`protocol_view<T>`, to the C++ Standard Library. Both classes support |
| 9 | +structural-subtyping, `protocol` is owning, `protocol_view` is non-owning. |
9 | 10 |
|
10 | | -## Overview |
| 11 | +See [DRAFT.md](DRAFT.md) for more details on design. |
11 | 12 |
|
12 | | -Polymorphic interfaces in C++ traditionally require explicit inheritance from an |
13 | | -abstract base class. This nominal subtyping tightly couples independent |
14 | | -components, makes it impossible to retroactively apply interfaces to third-party |
15 | | -types, and typically forces reference semantics (e.g., `std::unique_ptr`). |
16 | | - |
17 | | -Inspired by Python's `Protocol` (PEP 544), this repository explores bringing a |
18 | | -similar paradigm to C++. By using AST parsing (via Clang) and code generation, |
19 | | -the tool synthesizes type-erased wrappers that accept any type structurally |
20 | | -conforming to an interface, without inheritance. |
21 | | - |
22 | | -These protocols maintain deep-copy value semantics, strict `const`-propagation, |
23 | | -and allocator awareness, consistent with the design of `jbcoe/value_types` |
24 | | -(P3019). |
| 13 | +This repository contains both the ISO C++ proposal to add these new library |
| 14 | +types and a reference implementation. The reference implementation is currently |
| 15 | +reliant on a Python code-generation step as C++26 reflection is missing some of |
| 16 | +the features needed to generate code needed by these types at compile time. |
25 | 17 |
|
26 | 18 | ## Standardization |
27 | 19 |
|
28 | | -As C++ reflection (P2996) matures and code injection is added in future |
29 | | -standards (C++29+), the generation approach demonstrated here via `py_cppmodel` |
30 | | -will be achievable natively within the language. |
31 | | - |
32 | | -A draft proposal is available in `DRAFT.md`. |
33 | | - |
34 | | -## Use |
35 | | - |
36 | | -The interface is a plain struct with no `virtual` keywords, no `= 0`, and no base |
37 | | -classes. |
38 | | - |
39 | | -```cpp |
40 | | -#pragma once |
41 | | -#include <string> |
42 | | -#include <vector> |
43 | | - |
44 | | -namespace xyz { |
45 | | - |
46 | | -struct B { |
47 | | - void process(const std::string& input); |
48 | | - std::vector<int> get_results() const; |
49 | | - bool is_ready() const; |
50 | | -}; |
51 | | - |
52 | | -} // namespace xyz |
53 | | -``` |
54 | | -
|
55 | | -Write your concrete type. It does not need to inherit from `xyz::B`; it only |
56 | | -needs to structurally provide the methods defined in the interface. |
57 | | -
|
58 | | -```cpp |
59 | | -namespace xyz { |
60 | | -
|
61 | | -class MyImplementation { |
62 | | - std::vector<int> results_; |
63 | | - bool ready_ = false; |
64 | | -
|
65 | | - public: |
66 | | - // Structurally matches xyz::B |
67 | | - void process(const std::string& input) { |
68 | | - results_.push_back(input.length()); |
69 | | - ready_ = true; |
70 | | - } |
71 | | - std::vector<int> get_results() const { return results_; } |
72 | | - bool is_ready() const { return ready_; } |
73 | | -}; |
74 | | -
|
75 | | -} // namespace xyz |
76 | | -``` |
77 | | - |
78 | | -`xyz::protocol<xyz::B>` is an automatically generated type-erased wrapper. It |
79 | | -copies deeply, propagates `const` correctly, and supports custom allocators. |
80 | | - |
81 | | -```cpp |
82 | | -#include "generated/protocol_B.h" |
83 | | - |
84 | | -void run_pipeline(xyz::protocol<xyz::B> worker) { |
85 | | - if (!worker.is_ready()) { |
86 | | - worker.process("hello protocols"); |
87 | | - } |
88 | | - |
89 | | - for (int result : worker.get_results()) { |
90 | | - // ... |
91 | | - } |
92 | | -} |
93 | | - |
94 | | -int main() { |
95 | | - // Construct the protocol in-place with our implementation |
96 | | - xyz::protocol<xyz::B> p(std::in_place_type<xyz::MyImplementation>); |
97 | | - |
98 | | - run_pipeline(p); // Pass by value! |
99 | | - return 0; |
100 | | -} |
101 | | -``` |
102 | | -
|
103 | | -The generated wrapper uses C++20 concepts and `requires` clauses: any structural |
104 | | -mismatch produces clear, pinpointed compile-time errors rather than deeply nested |
105 | | -template instantiation failures. |
106 | | -
|
107 | | -```cpp |
108 | | -class BadImplementation { |
109 | | - public: |
110 | | - void process(const std::string& input); |
111 | | - // ERROR: Missing get_results() |
112 | | - // ERROR: is_ready() is missing 'const' |
113 | | - bool is_ready(); |
114 | | -}; |
115 | | -
|
116 | | -// COMPILER ERROR: |
117 | | -// constraints not satisfied |
118 | | -// the required expression 'std::as_const(t).is_ready()' is invalid |
119 | | -``` |
120 | | - |
121 | | -## `protocol_view`: Non-Owning Structural Subtyping |
122 | | - |
123 | | -Alongside `protocol`, the code generator also produces a `protocol_view` |
124 | | -specialization. While `protocol` manages the lifecycle of the underlying object |
125 | | -(with deep-copy value semantics), `protocol_view` is a lightweight, non-owning |
126 | | -reference, analogous to `std::string_view` or `std::span`, but for protocols. |
127 | | - |
128 | | -```cpp |
129 | | -// `view` observes the object without owning or copying it. |
130 | | -void inspect(xyz::protocol_view<xyz::B> view) { |
131 | | - if (view.is_ready()) { |
132 | | - // ... |
133 | | - } |
134 | | -} |
135 | | - |
136 | | -int main() { |
137 | | - xyz::MyImplementation impl; |
138 | | - |
139 | | - // Implicitly constructs a view over `impl` since it fulfills the structural requirements. |
140 | | - inspect(impl); |
141 | | - |
142 | | - xyz::protocol<xyz::B> p(std::in_place_type<xyz::MyImplementation>); |
143 | | - // Implicitly constructs a view over `p` as the protocol itself satisfies the requirements. |
144 | | - inspect(p); |
145 | | - |
146 | | - return 0; |
147 | | -} |
148 | | -``` |
149 | | -
|
150 | | -`protocol_view` provides non-owning, allocation-free structural dispatch at |
151 | | -function boundaries, avoiding deep copies while dispatching through a |
152 | | -lightweight indirection. |
153 | | -
|
154 | | -## Implementation Details and Benchmarks |
155 | | -
|
156 | | -The code generator generates a struct-of-function-pointers representing the vtable, managing type-erasure and dispatch via pointer indirection (manual vtables). This enforces constraints (value semantics, `const` correctness, and custom allocators) without requiring standard inheritance or compiler-generated virtual tables. |
157 | | -
|
158 | | -The library provides a `protocol_benchmark` target for measuring the performance of the protocol across allocations, copies, moves, and member function calls. |
159 | | -
|
160 | | -```bash |
161 | | -# Build and run the benchmark |
162 | | -./scripts/cmake.sh benchmark |
163 | | -``` |
| 20 | +The paper [P4148R2](https://wg21.link/P4148R2.pdf) (derived from |
| 21 | +[DRAFT.md](DRAFT.md)) was presented to the C++ Standard Library Incubator |
| 22 | +working group in Brno on June 11th 2026. The authors have been encouraged to |
| 23 | +continue work. |
164 | 24 |
|
165 | 25 | ## Contributing and Development |
166 | 26 |
|
167 | 27 | For build instructions, testing, contributing guidelines, and a deeper look into |
168 | | -the code generation architecture, see the [Developer Guide](CONTRIBUTING.md). |
169 | | - |
170 | | -## References |
171 | | - |
172 | | -- PEP 544: [Protocols: Structural subtyping (static duck |
173 | | - typing)](https://peps.python.org/pep-0544/) |
174 | | - |
175 | | -- P3019: [std::indirect and |
176 | | - std::polymorphic](https://isocpp.org/files/papers/P3019R14.pdf) |
| 28 | +the code generation architecture, see [CONTRIBUTING.md](CONTRIBUTING.md). |
177 | 29 |
|
178 | | -- P2996: [Reflection for C++26](https://isocpp.org/files/papers/P2996R13.html) |
| 30 | +## GitHub codespaces |
179 | 31 |
|
180 | | -- py_cppmodel: [Python wrappers for clang's parsing of |
181 | | - C++](https://github.com/jbcoe/py_cppmodel) |
| 32 | +Press `.` or visit [https://github.dev/jbcoe/cc-protocol] to open the project in |
| 33 | +an instant, cloud-based, development environment. We have defined a |
| 34 | +[devcontainer](.devcontainer/devcontainer.json) that will automatically install |
| 35 | +the dependencies required to build and test the project. |
0 commit comments