From c871e434cda78979b13d26caa502def2e034827c Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Wed, 1 Apr 2026 20:58:20 +0100 Subject: [PATCH 1/2] Placeholders for human work --- DRAFT.md | 219 +++++++++++++------------------------------------------ 1 file changed, 49 insertions(+), 170 deletions(-) diff --git a/DRAFT.md b/DRAFT.md index f2a1802..5b20944 100644 --- a/DRAFT.md +++ b/DRAFT.md @@ -18,14 +18,19 @@ _Philip Craig \<\>_ ## Abstract -We propose `protocol` and `protocol_view`, standard library vocabulary -types for structural subtyping in C++. Interfaces are specified as plain structs; -any type whose member functions satisfy the interface is accepted without -requiring explicit inheritance. The owning type, `protocol`, provides value -semantics with deep-copy behaviour. The non-owning type, `protocol_view`, -provides a lightweight reference to any conforming type, analogous to -`std::span`. Both types are generated automatically by the compiler using static -reflection, eliminating hand-written type-erasure boilerplate. +We propose `protocol` and `protocol_view`, standard library vocabulary +types for structural subtyping in C++. Interfaces are specified as plain +structs; any type whose member functions satisfy the interface is accepted +without requiring explicit inheritance. + +The owning type, `protocol`, provides value semantics with deep-copy behaviour +and support for custom allocators. + +The non-owning type, `protocol_view`, provides a lightweight reference to any +conforming type, analogous to `std::span`. + +Ideally both types would be generated by the compiler using static reflection, +eliminating hand-written type-erasure boilerplate. ## History @@ -35,196 +40,64 @@ reflection, eliminating hand-written type-erasure boilerplate. ## Motivation -Traditional polymorphism in C++ requires inheritance from a common base class. -This coupling prevents the retroactive application of interfaces to existing -types and mandates reference semantics, which complicates ownership reasoning. - -`std::function` provides structural subtyping for single callable objects, but -no standardised equivalent exists for interfaces comprising multiple member -functions. Developers must author bespoke type-erased wrappers, incurring -substantial boilerplate for vtable construction, storage management, and -const-propagation. Such wrappers are difficult to write correctly and must be -duplicated for every interface. - -Structural subtyping is well-established in other languages. PEP 544 introduced -Protocols in Python: a class that structurally provides the required methods is -considered a subtype without explicit inheritance. We propose an equivalent -mechanism for C++. - -We propose `protocol` to own an object of any type that satisfies interface -`I`, providing value semantics and deep copy. We propose `protocol_view` to -refer non-owningly to any conforming type, enabling efficient observation at -function boundaries without allocation or ownership transfer. - -Nominal subtyping allows non-owning polymorphism via raw pointers or references -(`Base*`, `Base&`). Structural subtyping has no native equivalent. `protocol_view` -fills this gap and is to `protocol` as `std::span` is to `std::vector`. +TODO(Phil): Why would developers want structural subtyping in C++? ## Examples -The following examples illustrate the use of `protocol` for value-semantic -ownership and `protocol_view` for non-owning observation. Note that `Circle` -does not inherit from `Drawable`; it satisfies the interface structurally. - -```cpp -struct Drawable { - std::string_view name() const; - void draw(); - int draw_count() const; -}; +### Minimal API examples -struct Circle { - std::string_view name() const { return "Circle"; } - void draw() { ++draw_count_; } - int draw_count() const { return draw_count_; } +TODO(jbcoe): Simple illustration of the API for `protocol` and `protocol_view`. -private: - int draw_count_ = 0; -}; -``` +### Market data example -### `protocol` and value semantics +TODO(Ant): Provide (small but realistic) example using market data structs from +exchanges. -`protocol` owns its contained object. Copying a `protocol` performs a -deep copy of the underlying object. +### Rendering systems example -```cpp -// Construct in-place -protocol p1(std::in_place_type); +TODO(Ant): Provide (small but realistic) example using rendering systems from +games. -// p2 is a deep copy of p1, including the underlying Circle object -protocol p2 = p1; +### Function-like examples (with a table) -p1.draw(); -p1.draw(); +TODO(Ant) -// p1 and p2 are independent -assert(p1.draw_count() == 2); -assert(p2.draw_count() == 0); +```c++ +template +struct ConstFunction { + R operator()(Args&&... args) const; +}; ``` -### `protocol_view` and reference semantics - -`protocol_view` is a non-owning view of any type satisfying interface -`I`. Copying a `protocol_view` is a shallow operation; both copies refer to the -same underlying object. - -```cpp -void print_name(protocol_view view) { - // A const view permits only const member functions. - std::cout << "Name: " << view.name() << "\n"; -} - -Circle circle; - -// Bind a view to a concrete object without allocation or ownership transfer. -print_name(circle); - -protocol p(std::in_place_type); - -// Bind a view to an owning protocol object. -print_name(p); - -// Copying a view is shallow; both views refer to the same Circle. -protocol_view v1(circle); -protocol_view v2 = v1; -v2.draw(); -assert(circle.draw_count() == 1); +```c++ +protocol> my_func; ``` ## Design -### Requirements - -We require the following properties of `protocol` and `protocol_view`. - -A type satisfies an interface based solely on the presence of member functions -with conforming signatures; explicit inheritance is not required. - -`protocol` provides value semantics. Copying a `protocol` object performs a -deep copy of the underlying erased object. Moving a `protocol` object leaves it -in a valid but unspecified (valueless) state, enabling efficient move operations -without heap allocation. - -`protocol_view` provides non-owning reference semantics. It is constructible -from any structurally conforming type, including `protocol` itself. Method -calls are forwarded through a synthesised vtable. - -Const-correctness is strictly maintained. A `const`-qualified `protocol` object, -or a `protocol_view`, permits the invocation of only `const`-qualified -member functions of the underlying erased object. - -The owning `protocol` is fully allocator-aware and properly supports -`std::allocator_traits`. - -The implementation of both types is generated automatically by the compiler -using reflection, eliminating the need for manually authored boilerplate. - -### Design decisions +TODO(Ant & Phil): Adopt design wording from value-types. Add wording for +protocol-adopted member functions. -**Interface specification as a plain struct.** We chose to specify interfaces as -unannotated structs rather than introducing a new declaration syntax. This -decision favours minimal language impact and allows existing structs to serve as -interface definitions without modification. +Add design wording for `protocol_view`. -**Valueless-after-move state for `protocol`.** We require that a moved-from -`protocol` enters a valueless state rather than retaining its previous value. -This is consistent with `std::variant` and avoids the cost of constructing a -sentinel object on move. Calling interface methods on a valueless `protocol` -object is undefined behaviour. - -**`protocol_view` does not own.** We considered whether `protocol_view` should -support owning a small buffer optimisation. We rejected this in favour of a -strict non-owning contract, consistent with `std::string_view` and `std::span`. -Users requiring ownership should use `protocol`. - -**Allocator awareness.** We require `protocol` to support `std::allocator_traits` -for consistency with other owning standard library containers. `protocol_view` -requires no allocation and is therefore not allocator-aware. +### Comparison with proxy ## Impact on the Standard -This is a library proposal that fundamentally depends on core-language reflection -facilities capable of programmatic class generation. - -C++26 reflection (P2996) provides the introspection capabilities required to -validate structural conformity at compile time. However, synthesising a protocol -wrapper requires the ability to inject member functions into a class -definition—a generative capability not present in C++26. This proposal therefore -serves as a motivating use case for future extensions to the language's -reflection and code-injection facilities. +This proposal is a library extension. It requires language support for code +injection from static reflection and the addition of a new standard library +header ``." ## Technical Specifications -The synthesis of `protocol` and `protocol_view` relies on static reflection. - -### Elements implementable with C++26 reflection - -C++26 reflection (P2996) provides sufficient introspection for structural -validation. Using the reflection operator (`^`) and the `std::meta` namespace, a -metaprogram can extract the names, signatures, and `const`-qualifiers of the -member functions declared in an interface struct. It can then verify that a -candidate type provides matching member functions, replacing manually authored -concepts with a generic, reflection-driven structural constraint. +TODO(jbcoe): Adopt relevant wording from value-types, add wording for reflected +methods. -### Elements requiring post-C++26 additions +## Polls -C++26 reflection does not yet support generalised code injection. The following -elements of the implementation require future standard additions. +- Arrow rather than dot syntax for member functions? -The dispatch mechanism—whether a virtual hierarchy or a synthesised vtable— -requires injection of virtual functions or function pointers derived from an -introspected interface. `std::meta::define_class` is currently restricted to -non-static data members and does not support member function injection. - -Generating the forwarding member functions of the `protocol` wrapper likewise -requires member function injection. Expanding introspected function parameters -into parameter packs for forwarding requires range-splicing of function arguments -and return types, which depends on more advanced injection proposals beyond P2996. - -Until such injection facilities are standardised, a practical implementation -requires external tooling for code generation, with C++26 reflection handling -structural validation. +- Should we work to standardize `protocol`? ## Reference Implementation @@ -246,5 +119,11 @@ this proposal. [P2996] _Reflection for C++26_. +[Metaclasses for generative C++] + + +[P3086R4 Proxy] + + [py_cppmodel] _Python wrappers for clang's parsing of C++ to simplify AST analysis_. From 9ad97b18e119704547e2744ce147c03ec2961df6 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Wed, 8 Apr 2026 22:54:38 +0100 Subject: [PATCH 2/2] Some changes to iterate upon --- DRAFT.md | 141 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/DRAFT.md b/DRAFT.md index 5b20944..e394864 100644 --- a/DRAFT.md +++ b/DRAFT.md @@ -6,7 +6,7 @@ D4148R0 Working Group: Library Evolution, Library -Date: 2026-03-25 +Date: 2026-04-8 _Jonathan Coe \<\>_ @@ -16,21 +16,30 @@ _Antony Peacock \<\>_ _Philip Craig \<\>_ +_Neelofer Banglawala \<\>_ + ## Abstract We propose `protocol` and `protocol_view`, standard library vocabulary -types for structural subtyping in C++. Interfaces are specified as plain -structs; any type whose member functions satisfy the interface is accepted -without requiring explicit inheritance. +types for structural subtyping in C++. Interfaces are specified as plain structs; +any type whose member functions satisfy the interface is accepted without +requiring explicit inheritance. + +Any type that provides member functions with the same names and function +signatures as those specified by the interface is considered to be _conforming_ +to the protocol. -The owning type, `protocol`, provides value semantics with deep-copy behaviour -and support for custom allocators. +The owning type, `protocol`, provides value semantics (const-propagation and +deep-copies) and support for custom allocators for any conforming type. The non-owning type, `protocol_view`, provides a lightweight reference to any conforming type, analogous to `std::span`. -Ideally both types would be generated by the compiler using static reflection, -eliminating hand-written type-erasure boilerplate. +Ideally both `protocol` and `protocol_view` would be generated by the compiler +using static reflection, eliminating hand-written type-erasure boilerplate or +custom build steps. This proposal assumes the availability of static reflection +and code injection and focuses solely on the design of the class templates +`protocol` and `protocol_view`. ## History @@ -40,7 +49,33 @@ eliminating hand-written type-erasure boilerplate. ## Motivation -TODO(Phil): Why would developers want structural subtyping in C++? +Using `protocol`, functions can be written with run-time polymorphism +(supporting mutliple types) without the types being related through inheritance. +This is useful when types come from third-party libraries and cannot be readily +altered to fit within a user-defined type heirarchy. + +Currently, hand-written type-erasure or interface-specific solutions like +`copyable_function` must be used for run-time polymorphism through structural +subtyping. + +## Design + +In C++26 we introduced `polymorphic` which confers value-like semantics on a +dynamically-allocated object. A `polymorphic` may hold an object of a class +publicly derived from `T`. In this proposal, we seek to further extend C++'s +library of value-types with `protocol` which can hold an object of any type +so long as that type is a structural sub-type of `T`. + +Like `polymorphic`, `protocol` supports deep-copies, const propagation and +custom allocators. Like `polymorphic`, `protocol` has a valueless state after +after being moved from to allow move construction and move assignment without +dynamic memory allocation. + +Where `polymorphic` is owning, `T*`, or `const T*` can be used as a +non-owning reference type. There is no base class to take a pointer to for +`protocol` so we propose the addition of `protocol_view` (and +`protocol_view`) which are similar to `span` and `string_view` and give +reference semantics to structural sub-types. ## Examples @@ -48,37 +83,81 @@ TODO(Phil): Why would developers want structural subtyping in C++? TODO(jbcoe): Simple illustration of the API for `protocol` and `protocol_view`. -### Market data example +### Function-like examples -TODO(Ant): Provide (small but realistic) example using market data structs from -exchanges. +We can use `protocol` and `protocol_view` with appropriate +structural types to implement and extend the standard library's +existing set of function-objects: -### Rendering systems example +```c++ +template +struct Function { + // All special member functions are defaulted. + R operator()(Args&&... args) const; +}; +``` -TODO(Ant): Provide (small but realistic) example using rendering systems from -games. +```c++ +template +struct MoveOnlyFunction { + // Deleted copy constructor and copy assignment. + MoveOnlyFunction(const MoveOnlyFunction&) = delete; + MoveOnlyFunction& operator=(const MoveOnlyFunction&) = delete; -### Function-like examples (with a table) + // Defaulted move constructor and move assignment. + MoveOnlyFunction(MoveOnlyFunction&&) = default; + MoveOnlyFunction& operator=(MoveOnlyFunction&&) = default; -TODO(Ant) + R operator()(Args&&... args) const; +}; +``` ```c++ template -struct ConstFunction { - R operator()(Args&&... args) const; +struct MutatingFunction { + // All special member functions are defaulted. + R operator()(Args&&... args); +}; +``` + +```c++ +template +struct MoveOnlyMutatingFunction { + // Deleted copy constructor and copy assignment. + MoveOnlyFunction(const MoveOnlyFunction&) = delete; + MoveOnlyFunction& operator=(const MoveOnlyFunction&) = delete; + + // Defaulted move constructor and move assignment. + MoveOnlyFunction(MoveOnlyFunction&&) = default; + MoveOnlyFunction& operator=(MoveOnlyFunction&&) = default; + + R operator()(Args&&... args); }; ``` ```c++ -protocol> my_func; +struct OverloadedFunction { + R1 operator()(Args1&&... args) const; + R2 operator()(Args2&&... args); + R3 operator()(Args3&&... args); +}; ``` -## Design +There is currently no function-type in the standard library that can handle an +overload set. The table below is illustrative of how flexible `protocol` and +`protocol_view` are. -TODO(Ant & Phil): Adopt design wording from value-types. Add wording for -protocol-adopted member functions. +| Standard library type | Protocol equivalent | +| :--- | :--- | +| `std::copyable_function` | `protocol>` | +| `std::move_only_function` | `protocol>` | +| `std::function_ref` | `protocol_view>` | +| `std::copyable_function` | `protocol>` | +| `std::move_only_function` | `protocol>` | +| `std::function_ref` | `protocol_view>` | +| ??? | `protocol` | +| ??? | `protocol_view` | -Add design wording for `protocol_view`. ### Comparison with proxy @@ -95,17 +174,15 @@ methods. ## Polls -- Arrow rather than dot syntax for member functions? - -- Should we work to standardize `protocol`? +- Should we work to standardize `protocol` and `protocol_view`? ## Reference Implementation -A reference implementation using an AST-based Python code generator -(`py_cppmodel`) to simulate post-C++26 code injection is available at -[py_cppmodel]. The implementation demonstrates the feasibility of vtable -generation, allocator awareness, and the value semantics properties required by -this proposal. +A reference implementation, using an AST-based Python code generator to simulate +post-C++26 code injection, is available at +. The implementation demonstrates the +feasibility of vtable generation, allocator awareness, and the value semantics +properties required by this proposal. ## Acknowledgements