66
77Working Group: Library Evolution, Library
88
9- Date: 2026-03-25
9+ Date: 2026-04-8
1010
1111_ Jonathan Coe \< < jonathanbcoe@gmail.com > \> _
1212
@@ -16,16 +16,30 @@ _Antony Peacock \<<ant.peacock@gmail.com>\>_
1616
1717_ Philip Craig \< < philip@pobox.com > \> _
1818
19+ _ Neelofer Banglawala \< <dr.nbanglawala@gmail.com >\> _
20+
1921## Abstract
2022
21- We propose ` protocol ` and ` protocol_view ` , standard library vocabulary
23+ We propose ` protocol<T, A> ` and ` protocol_view<T> ` , standard library vocabulary
2224types for structural subtyping in C++. Interfaces are specified as plain structs;
2325any type whose member functions satisfy the interface is accepted without
24- requiring explicit inheritance. The owning type, ` protocol ` , provides value
25- semantics with deep-copy behaviour. The non-owning type, ` protocol_view ` ,
26- provides a lightweight reference to any conforming type, analogous to
27- ` std::span ` . Both types are generated automatically by the compiler using static
28- reflection, eliminating hand-written type-erasure boilerplate.
26+ requiring explicit inheritance.
27+
28+ Any type that provides member functions with the same names and function
29+ signatures as those specified by the interface is considered to be _ conforming_
30+ to the protocol.
31+
32+ The owning type, ` protocol ` , provides value semantics (const-propagation and
33+ deep-copies) and support for custom allocators for any conforming type.
34+
35+ The non-owning type, ` protocol_view ` , provides a lightweight reference to any
36+ conforming type, analogous to ` std::span ` .
37+
38+ Ideally both ` protocol ` and ` protocol_view ` would be generated by the compiler
39+ using static reflection, eliminating hand-written type-erasure boilerplate or
40+ custom build steps. This proposal assumes the availability of static reflection
41+ and code injection and focuses solely on the design of the class templates
42+ ` protocol ` and ` protocol_view ` .
2943
3044## History
3145
@@ -35,204 +49,140 @@ reflection, eliminating hand-written type-erasure boilerplate.
3549
3650## Motivation
3751
38- Traditional polymorphism in C++ requires inheritance from a common base class.
39- This coupling prevents the retroactive application of interfaces to existing
40- types and mandates reference semantics, which complicates ownership reasoning.
52+ Using ` protocol ` , functions can be written with run-time polymorphism
53+ (supporting mutliple types) without the types being related through inheritance.
54+ This is useful when types come from third-party libraries and cannot be readily
55+ altered to fit within a user-defined type heirarchy.
4156
42- ` std::function ` provides structural subtyping for single callable objects, but
43- no standardised equivalent exists for interfaces comprising multiple member
44- functions. Developers must author bespoke type-erased wrappers, incurring
45- substantial boilerplate for vtable construction, storage management, and
46- const-propagation. Such wrappers are difficult to write correctly and must be
47- duplicated for every interface.
57+ Currently, hand-written type-erasure or interface-specific solutions like
58+ ` copyable_function ` must be used for run-time polymorphism through structural
59+ subtyping.
4860
49- Structural subtyping is well-established in other languages. PEP 544 introduced
50- Protocols in Python: a class that structurally provides the required methods is
51- considered a subtype without explicit inheritance. We propose an equivalent
52- mechanism for C++.
61+ ## Design
62+
63+ In C++26 we introduced ` polymorphic<T> ` which confers value-like semantics on a
64+ dynamically-allocated object. A ` polymorphic<T> ` may hold an object of a class
65+ publicly derived from ` T ` . In this proposal, we seek to further extend C++'s
66+ library of value-types with ` protocol<T> ` which can hold an object of any type
67+ so long as that type is a structural sub-type of ` T ` .
5368
54- We propose ` protocol<I> ` to own an object of any type that satisfies interface
55- ` I ` , providing value semantics and deep copy. We propose ` protocol_view<I> ` to
56- refer non-owningly to any conforming type, enabling efficient observation at
57- function boundaries without allocation or ownership transfer .
69+ Like ` polymorphic ` , ` protocol ` supports deep-copies, const propagation and
70+ custom allocators. Like ` polymorphic ` , ` protocol ` has a valueless state after
71+ after being moved from to allow move construction and move assignment without
72+ dynamic memory allocation.
5873
59- Nominal subtyping allows non-owning polymorphism via raw pointers or references
60- (` Base* ` , ` Base& ` ). Structural subtyping has no native equivalent. ` protocol_view `
61- fills this gap and is to ` protocol ` as ` std::span ` is to ` std::vector ` .
74+ Where ` polymorphic<T> ` is owning, ` T* ` , or ` const T* ` can be used as a
75+ non-owning reference type. There is no base class to take a pointer to for
76+ ` protocol<T> ` so we propose the addition of ` protocol_view<T> ` (and
77+ ` protocol_view<const T> ` ) which are similar to ` span ` and ` string_view ` and give
78+ reference semantics to structural sub-types.
6279
6380## Examples
6481
65- The following examples illustrate the use of ` protocol ` for value-semantic
66- ownership and ` protocol_view ` for non-owning observation. Note that ` Circle `
67- does not inherit from ` Drawable ` ; it satisfies the interface structurally.
82+ ### Minimal API examples
6883
69- ``` cpp
70- struct Drawable {
71- std::string_view name() const;
72- void draw();
73- int draw_count() const;
74- };
84+ TODO(jbcoe): Simple illustration of the API for ` protocol ` and ` protocol_view ` .
7585
76- struct Circle {
77- std::string_view name() const { return "Circle"; }
78- void draw() { ++draw_count_ ; }
79- int draw_count() const { return draw_count_ ; }
86+ ### Function-like examples
8087
81- private:
82- int draw_count_ = 0;
88+ We can use ` protocol ` and ` protocol_view ` with appropriate
89+ structural types to implement and extend the standard library's
90+ existing set of function-objects:
91+
92+ ``` c++
93+ template <typename R, typename ... Args>
94+ struct Function {
95+ // All special member functions are defaulted.
96+ R operator()(Args&&... args) const;
8397};
8498```
8599
86- ### `protocol` and value semantics
87-
88- `protocol<I>` owns its contained object. Copying a `protocol` performs a
89- deep copy of the underlying object.
100+ ```c++
101+ template <typename R, typename... Args>
102+ struct MoveOnlyFunction {
103+ // Deleted copy constructor and copy assignment.
104+ MoveOnlyFunction(const MoveOnlyFunction&) = delete;
105+ MoveOnlyFunction& operator=(const MoveOnlyFunction&) = delete;
90106
91- ```cpp
92- // Construct in-place
93- protocol<Drawable> p1(std::in_place_type<Circle>) ;
107+ // Defaulted move constructor and move assignment.
108+ MoveOnlyFunction(MoveOnlyFunction&&) = default;
109+ MoveOnlyFunction& operator=(MoveOnlyFunction&&) = default ;
94110
95- // p2 is a deep copy of p1, including the underlying Circle object
96- protocol<Drawable> p2 = p1;
97-
98- p1.draw();
99- p1.draw();
100-
101- // p1 and p2 are independent
102- assert(p1.draw_count() == 2);
103- assert(p2.draw_count() == 0);
111+ R operator()(Args&&... args) const;
112+ };
104113```
105114
106- ### ` protocol_view ` and reference semantics
107-
108- ` protocol_view<I> ` is a non-owning view of any type satisfying interface
109- ` I ` . Copying a ` protocol_view ` is a shallow operation; both copies refer to the
110- same underlying object.
111-
112- ``` cpp
113- void print_name (protocol_view<const Drawable > view) {
114- // A const view permits only const member functions.
115- std::cout << "Name: " << view.name() << "\n";
116- }
117-
118- Circle circle;
119-
120- // Bind a view to a concrete object without allocation or ownership transfer.
121- print_name(circle);
122-
123- protocol<Drawable > p(std::in_place_type<Circle >);
124-
125- // Bind a view to an owning protocol object.
126- print_name(p);
127-
128- // Copying a view is shallow; both views refer to the same Circle.
129- protocol_view<Drawable > v1(circle);
130- protocol_view<Drawable > v2 = v1;
131- v2.draw();
132- assert(circle.draw_count() == 1);
115+ ``` c++
116+ template <typename R, typename ... Args>
117+ struct MutatingFunction {
118+ // All special member functions are defaulted.
119+ R operator()(Args&&... args);
120+ };
133121```
134122
135- ## Design
136-
137- ### Requirements
138-
139- We require the following properties of `protocol` and `protocol_view`.
140-
141- A type satisfies an interface based solely on the presence of member functions
142- with conforming signatures; explicit inheritance is not required.
123+ ```c++
124+ template <typename R, typename... Args>
125+ struct MoveOnlyMutatingFunction {
126+ // Deleted copy constructor and copy assignment.
127+ MoveOnlyFunction(const MoveOnlyFunction&) = delete;
128+ MoveOnlyFunction& operator=(const MoveOnlyFunction&) = delete;
143129
144- `protocol<I>` provides value semantics. Copying a `protocol` object performs a
145- deep copy of the underlying erased object. Moving a `protocol` object leaves it
146- in a valid but unspecified (valueless) state, enabling efficient move operations
147- without heap allocation.
130+ // Defaulted move constructor and move assignment.
131+ MoveOnlyFunction(MoveOnlyFunction&&) = default;
132+ MoveOnlyFunction& operator=(MoveOnlyFunction&&) = default;
148133
149- `protocol_view<I>` provides non-owning reference semantics. It is constructible
150- from any structurally conforming type, including `protocol<I>` itself. Method
151- calls are forwarded through a synthesised vtable.
152-
153- Const-correctness is strictly maintained. A `const`-qualified `protocol` object,
154- or a `protocol_view<const I>`, permits the invocation of only `const`-qualified
155- member functions of the underlying erased object.
156-
157- The owning `protocol` is fully allocator-aware and properly supports
158- `std::allocator_traits`.
159-
160- The implementation of both types is generated automatically by the compiler
161- using reflection, eliminating the need for manually authored boilerplate.
134+ R operator()(Args&&... args);
135+ };
136+ ```
162137
163- ### Design decisions
138+ ``` c++
139+ struct OverloadedFunction {
140+ R1 operator()(Args1&&... args) const;
141+ R2 operator()(Args2&&... args);
142+ R3 operator()(Args3&&... args);
143+ };
144+ ```
164145
165- **Interface specification as a plain struct.** We chose to specify interfaces as
166- unannotated structs rather than introducing a new declaration syntax. This
167- decision favours minimal language impact and allows existing structs to serve as
168- interface definitions without modification.
146+ There is currently no function-type in the standard library that can handle an
147+ overload set. The table below is illustrative of how flexible `protocol` and
148+ `protocol_view` are.
169149
170- **Valueless-after-move state for `protocol`.** We require that a moved-from
171- `protocol` enters a valueless state rather than retaining its previous value.
172- This is consistent with `std::variant` and avoids the cost of constructing a
173- sentinel object on move. Calling interface methods on a valueless `protocol`
174- object is undefined behaviour.
150+ | Standard library type | Protocol equivalent |
151+ | :--- | :--- |
152+ | `std::copyable_function<R(Args...) const>` | `protocol<Function<R, Args...>>` |
153+ | `std::move_only_function<R(Args...) const>` | `protocol<MoveOnlyFunction<R, Args...>>` |
154+ | `std::function_ref<R(Args...) const>` | `protocol_view<Function<R, Args...>>` |
155+ | `std::copyable_function<R(Args...)>` | `protocol<MutatingFunction<R, Args...>>` |
156+ | `std::move_only_function<R(Args...)>` | `protocol<MoveOnlyMutatingFunction<R, Args...>>` |
157+ | `std::function_ref<R(Args...)>` | `protocol_view<MutatingFunction<R, Args...>>` |
158+ | ??? | `protocol<OverloadedFunction>` |
159+ | ??? | `protocol_view<OverloadedFunction>` |
175160
176- **`protocol_view` does not own.** We considered whether `protocol_view` should
177- support owning a small buffer optimisation. We rejected this in favour of a
178- strict non-owning contract, consistent with `std::string_view` and `std::span`.
179- Users requiring ownership should use `protocol`.
180161
181- **Allocator awareness.** We require `protocol` to support `std::allocator_traits`
182- for consistency with other owning standard library containers. `protocol_view`
183- requires no allocation and is therefore not allocator-aware.
162+ ### Comparison with proxy
184163
185164## Impact on the Standard
186165
187- This is a library proposal that fundamentally depends on core-language reflection
188- facilities capable of programmatic class generation.
189-
190- C++26 reflection (P2996) provides the introspection capabilities required to
191- validate structural conformity at compile time. However, synthesising a protocol
192- wrapper requires the ability to inject member functions into a class
193- definition—a generative capability not present in C++26. This proposal therefore
194- serves as a motivating use case for future extensions to the language's
195- reflection and code-injection facilities.
166+ This proposal is a library extension. It requires language support for code
167+ injection from static reflection and the addition of a new standard library
168+ header `<protocol>`."
196169
197170## Technical Specifications
198171
199- The synthesis of `protocol` and `protocol_view` relies on static reflection.
200-
201- ### Elements implementable with C++26 reflection
202-
203- C++26 reflection (P2996) provides sufficient introspection for structural
204- validation. Using the reflection operator (`^`) and the `std::meta` namespace, a
205- metaprogram can extract the names, signatures, and `const`-qualifiers of the
206- member functions declared in an interface struct. It can then verify that a
207- candidate type provides matching member functions, replacing manually authored
208- concepts with a generic, reflection-driven structural constraint.
172+ TODO(jbcoe): Adopt relevant wording from value-types, add wording for reflected
173+ methods.
209174
210- ### Elements requiring post-C++26 additions
175+ ## Polls
211176
212- C++26 reflection does not yet support generalised code injection. The following
213- elements of the implementation require future standard additions.
214-
215- The dispatch mechanism—whether a virtual hierarchy or a synthesised vtable—
216- requires injection of virtual functions or function pointers derived from an
217- introspected interface. `std::meta::define_class` is currently restricted to
218- non-static data members and does not support member function injection.
219-
220- Generating the forwarding member functions of the `protocol` wrapper likewise
221- requires member function injection. Expanding introspected function parameters
222- into parameter packs for forwarding requires range-splicing of function arguments
223- and return types, which depends on more advanced injection proposals beyond P2996.
224-
225- Until such injection facilities are standardised, a practical implementation
226- requires external tooling for code generation, with C++26 reflection handling
227- structural validation.
177+ - Should we work to standardize `protocol` and `protocol_view`?
228178
229179## Reference Implementation
230180
231- A reference implementation using an AST-based Python code generator
232- (`py_cppmodel`) to simulate post-C++26 code injection is available at
233- [py_cppmodel]. The implementation demonstrates the feasibility of vtable
234- generation, allocator awareness, and the value semantics properties required by
235- this proposal.
181+ A reference implementation, using an AST-based Python code generator to simulate
182+ post-C++26 code injection, is available at
183+ <https://github.com/jbcoe/cc-protocol>. The implementation demonstrates the
184+ feasibility of vtable generation, allocator awareness, and the value semantics
185+ properties required by this proposal.
236186
237187## Acknowledgements
238188
@@ -246,5 +196,11 @@ this proposal.
246196
247197[P2996] _Reflection for C++26_. <https://isocpp.org/files/papers/P2996R13.html>
248198
199+ [Metaclasses for generative C++]
200+ <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0707r5.pdf>
201+
202+ [P3086R4 Proxy]
203+ <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3086r5.html>
204+
249205[py_cppmodel] _Python wrappers for clang's parsing of C++ to simplify AST
250206analysis_. <https://github.com/jbcoe/py_cppmodel>
0 commit comments