Skip to content
Merged
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
68 changes: 31 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Standard](https://img.shields.io/badge/C%2B%2B-20%2B-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B20)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)

An experimental C++ library and generation tool for enabling static structural
An experimental C++ library and code generation tool for static structural
subtyping (duck typing) with value semantics.

## Overview
Expand All @@ -15,26 +15,26 @@ components, makes it impossible to retroactively apply interfaces to third-party
types, and typically forces reference semantics (e.g., `std::unique_ptr`).

Inspired by Python's `Protocol` (PEP 544), this repository explores bringing a
similar paradigm to C++. By utilizing AST parsing (via Clang) and code
generation, we can automatically synthesize type-erased wrappers that accept any
type structurally conforming to an interface.
similar paradigm to C++. By using AST parsing (via Clang) and code generation,
the tool synthesizes type-erased wrappers that accept any type structurally
conforming to an interface, without inheritance.

Crucially, these protocols maintain deep-copy value semantics, strict
`const`-propagation, and allocator awareness, aligning heavily with the
principles of `jbcoe/value_types` (P3019).
These protocols maintain deep-copy value semantics, strict `const`-propagation,
and allocator awareness, consistent with the design of `jbcoe/value_types`
(P3019).

## Standardization

As C++ reflection (P2996) matures and advanced code injection capabilities are
added in future standards (C++29+), the generation process demonstrated here via
`py_cppmodel` will be achievable natively within the language.
As C++ reflection (P2996) matures and code injection is added in future
standards (C++29+), the generation approach demonstrated here via `py_cppmodel`
will be achievable natively within the language.

A draft proposal detailing this feature can be found in `proposals/DRAFT.md`.
A draft proposal is available in `DRAFT.md`.

## Use

Unlike traditional polymorphism, the interface is just a struct. No `virtual`
keywords, no `= 0`, and no base classes.
The interface is a plain struct with no `virtual` keywords, no `= 0`, and no base
classes.

```cpp
#pragma once
Expand All @@ -52,7 +52,7 @@ struct B {
} // namespace xyz
```

Write your concrete type. It does not need to inherit from `xyz::B`. It only
Write your concrete type. It does not need to inherit from `xyz::B`; it only
needs to structurally provide the methods defined in the interface.

```cpp
Expand All @@ -75,9 +75,8 @@ class MyImplementation {
} // namespace xyz
```

We can now use `xyz::protocol<xyz::B>`, an automatically generated type-erased
wrapper. It copies deeply, propagates `const` correctly, and supports custom
allocators.
`xyz::protocol<xyz::B>` is an automatically generated type-erased wrapper. It
copies deeply, propagates `const` correctly, and supports custom allocators.

```cpp
#include "generated/protocol_B.h"
Expand All @@ -102,7 +101,7 @@ int main() {
```

The generated wrapper uses C++20 concepts and `requires` clauses: any structural
mismatch emits clear, pinpointed compile-time errors rather than deeply nested
mismatch produces clear, pinpointed compile-time errors rather than deeply nested
template instantiation failures.

```cpp
Expand All @@ -123,9 +122,8 @@ class BadImplementation {

Alongside `protocol`, the code generator also produces a `protocol_view`
specialization. While `protocol` manages the lifecycle of the underlying object
(with deep-copy value semantics), `protocol_view` provides a lightweight,
non-owning reference. It functions similarly to `std::string_view` or
`std::span` but for protocols.
(with deep-copy value semantics), `protocol_view` is a lightweight, non-owning
reference, analogous to `std::string_view` or `std::span`, but for protocols.

```cpp
// `view` observes the object without owning or copying it.
Expand All @@ -142,35 +140,32 @@ int main() {
inspect(impl);

xyz::protocol<xyz::B> p(std::in_place_type<xyz::MyImplementation>);
// Implicitly constructs a view over `p` as the protocol itself satisfies the requirements!
// Implicitly constructs a view over `p` as the protocol itself satisfies the requirements.
inspect(p);

return 0;
}
```

A `protocol_view` provides true zero-overhead duck-typing at function
boundaries, decoupling types while avoiding the cost of allocations and deep
copies.
`protocol_view` provides non-owning, allocation-free structural dispatch at
function boundaries, avoiding deep copies while dispatching through a
lightweight indirection.

## Implementation Details and Benchmarks

The `protocol` code generator supports two different underlying dispatch
strategies:
The code generator supports two dispatch strategies:

1. Virtual Dispatch (Default): Generates a traditional C++ polymorphic class
hierarchy with `virtual` methods. The type-erased wrapper heap-allocates a
control block derived from a common interface.

2. Explicit Manual Vtables: Generates a struct-of-function-pointers representing
the vtable. This approach manually manages type-erasure and dispatch via
pointer indirection.
2. Manual Vtables: Generates a struct-of-function-pointers representing the
vtable, managing type-erasure and dispatch via pointer indirection.

Both implementations enforce identical constraints (value semantics, `const`
correctness, and custom allocators). The library builds both versions to ensure
they are strictly equivalent and offers a `protocol_benchmark` target to
directly compare their performance for allocations, copies, moves, and member
function calls.
correctness, and custom allocators). The library builds both versions to verify
equivalence and provides a `protocol_benchmark` target for directly comparing
their performance across allocations, copies, moves, and member function calls.

```bash
# Build and run the benchmark comparing the two implementations
Expand All @@ -179,9 +174,8 @@ function calls.

## Contributing and Development

For instructions on how to build, test, and contribute to this project, as well
as a deeper look into the code generation architecture, please refer to the
[Developer Guide](CONTRIBUTING.md).
For build instructions, testing, contributing guidelines, and a deeper look into
the code generation architecture, see the [Developer Guide](CONTRIBUTING.md).

## References

Expand Down
Loading