-
Notifications
You must be signed in to change notification settings - Fork 2
Compare protocol with proxy #26
Copy link
Copy link
Open
Description
There is another proposal in similar design space, https://github.com/ngcpp/proxy.
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3086r5.html
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3401r1.html
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3584r1.html
We should understand and compare similarities and differences.
AI-comparison:
Protocol vs. Proxy: A Comparison of Structural Subtyping in C++
This document outlines the fundamental differences between two prominent approaches to type-erased structural subtyping in C++: the protocol pattern (inspired by P3019 std::polymorphic and PEP 544, implemented as xyz::protocol) and the proxy pattern (proposed in P3086, implemented in ngcpp/proxy).
Fundamental Differences
1. Interface Definition (Reflection vs. Library Boilerplate)
- Protocol: Employs an unobtrusive approach. Interfaces are defined as standard C++
structs containing pure declarations. The library relies on (simulated or future C++26) static reflection to introspect thestructand generate a type-erased vtable wrapper. - Proxy: Employs a library-centric approach. Interfaces (called "Facades") are built using library templates (
pro::facade_builder) and macros (PRO_DEF_MEMBER_DISPATCH). The facade explicitly dictates the dispatch mechanism, conventions, and constraints.
2. Interaction Semantics (Value vs. Pointer)
- Protocol: Models true value semantics (Direct Semantics). You interact with a
protocol<T>exactly as you would a concrete value, using the dot (.) operator. It behaves like a polymorphic value container. - Proxy: Models pointer semantics. You interact with a
pro::proxy<F>as if it were a smart pointer, using the arrow (->) operator or passing the proxy to dispatch functors. This choice deliberately avoids name collisions between the wrapper's utility methods and the erased type's methods.
3. Configurability and "Meta-Properties"
- Protocol: Operates as a fixed container. Memory layout, copyability, and move behavior are determined by the
protocoltemplate implementation and standard C++ mechanisms (e.g., allocators). - Proxy: The Facade itself describes the "meta-properties" of the container. A facade can specify whether the type is copyable, trivially relocatable (for memmove optimizations), and even restrict the small-buffer optimization (SBO) size or pointer layout.
4. Subtyping and Substitution
- Protocol: Distinct generated types.
protocol<A>andprotocol<B>are unrelated C++ classes, even ifBis a superset ofA. - Proxy: Supports implicit downgrading. If a
RichFacadeincludes aLeanFacadeviabasic_facade_builder::add_facade, aproxy<RichFacade>can be transparently substituted where aproxy<LeanFacade>is expected.
Pattern Comparison Table
| Pattern | protocol (cc-protocol / P3019 style) |
proxy (ngcpp/proxy / P3086 style) |
|---|---|---|
| Defining an Interface | struct Draw {void draw() const;}; |
PRO_DEF_MEMBER_DISPATCH(MemDraw, draw);struct DrawFacade : pro::facade_builder::add_convention<MemDraw, void() const>::build {}; |
| Instantiation (Owning) | xyz::protocol<Draw> p = Circle{}; |
pro::proxy<DrawFacade> p = pro::make_proxy<DrawFacade>(Circle{}); |
| Method Invocation | p.draw(); |
p->draw(); (pointer semantics) |
| Non-Owning View | void render(xyz::protocol_view<Draw> v); |
void render(pro::proxy_view<DrawFacade> v); |
| Reassignment | p = Square{}; |
p = pro::make_proxy<DrawFacade>(Square{}); |
Patterns Exclusive to One Library
Implementable ONLY in proxy
1. Facade Downgrading / Substitution
- Pattern: Passing a
proxy<DrawableAndPrintable>to a function expectingproxy<Drawable>. - Missing Feature in
protocol:protocolgenerates entirely distinct, isolated classes for each interface. Because it lacks a relational composition mechanism likeadd_facade, the compiler has no way to map the vtable of a superset protocol to a subset protocol without a heavy, manual re-allocation/re-wrapping process.
2. Interface-Defined Layout and Relocatability
- Pattern: Forcing the polymorphic wrapper to be strictly 16 bytes (pointer + vtable) with no SBO, or declaring that the type can be optimally relocated via
memcpyto save cycles during vector reallocations. - Missing Feature in
protocol:protocoluses a "one-size-fits-all" container logic (likestd::functionorstd::any). It cannot bake memory constraints or relocation semantics into the structural interface itself, relying instead on standard C++ move constructors andstd::allocator.
3. Weak Handles
- Pattern: Creating a
pro::weak_proxythat observes a polymorphic object without extending its lifetime, analogous tostd::weak_ptr. - Missing Feature in
protocol:protocolcurrently only implements strict owning (protocol) and non-owning observation (protocol_view). It does not inherently support shared ownership semantics with weak referencing built-in.
Implementable ONLY in protocol
1. True Value Semantics ("Direct" Semantics)
- Pattern: Treating the polymorphic object literally like a value:
obj.do_something(). - Missing Feature in
proxy:proxyexplicitly separates the container from the object, enforcing pointer semantics (obj->do_something()). It lacks the generative injection necessary to synthesize member functions directly onto the wrapper that perfectly mirror the erased type.
2. Unobtrusive Interface Declarations
- Pattern: Using standard, unmodified C++ structs (or existing 3rd-party structs) as the interface definition.
- Missing Feature in
proxy:proxyrequires intrusive macro usage (PRO_DEF_MEMBER_DISPATCH) and specific library builder templates to create a Facade. It lacks the reflection capabilities required to automatically introspect a standard C++structand infer the dispatch conventions.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels