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
217 changes: 217 additions & 0 deletions presentations/protocol_2026_05_11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
---
marp: true
theme: default
paginate: true
size: 16:9
#footer: https://github.com/jbcoe/cc-protocol
---

# Structural Subtyping for C++

## [P4148R0](https://wg21.link/P4148R0)

### 11 May 2026

---

# Introduction

We propose adding two new class templates to the C++ Standard Library to support structural sub-typing at runtime:

```c++
template <typename T, typename Allocator = std::allocator<T>>
class protocol;
```

```c++
template <typename T>
class protocol_view;
```

---

# Polymorphism

polymorphism (from the Greek for "many forms") is the ability of different objects to respond to the same function call in their own specific ways.

C++ achieves polymorphism through mechanisms: Compile-time and Runtime.

---

# Compile-time Polymorphism

Overloaded functions or function templates allow the compiler to detemrine which function to call during the build process:

```c++
double func(const A& a);
double func(const B& b);
double func(const C& c);
```

```c++
<template typename T>
double func(const T& t);
```

---

# Runtime Polymorphism

Inheritance-based polymorphism enables runtime dispatch by using base-class interfaces to invoke overridden virtual functions across an extensible, open set of derived types.

```c++
struct Shape {
virtual size_t sides() const = 0;
virtual ~Shape() = default;
};

class Triangle : public Shape {
public:
size_t sides() const override { return 3; }
};

class Square : public Shape {
public:
size_t sides() const override { return 4; }
};
```

---

# Closed set Runtime Polymorphism

In C++ we can use `std::variant` and `std::visit` to implement closed-set polymorphism: runtime dispatch over a predefined set of types.

```c++
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };

...

std::variant<int, std::string> myData = "Polymorphic String";

std::visit(overload {
[](int val) {
std::cout << "Processing int: " << val * 2 << "\n";
},
[](const std::string& val) {
std::cout << "Processing string: " << val << " (length: " << val.size() << ")\n";
}
}, myData);
```

---

# Type erasure

Type erasure enables runtime polymorphism for an open set of unrelated types.

```c++
class AnyDrawable {
struct Interface {
virtual ~Interface() = default;
virtual void draw() = 0;
};

template<typename T> struct Model : Interface {
T d; Model(T t) : d(t) {}
void draw() override { d.draw(); }
};

Interface* i_;

void draw() { i_->draw(); }
};
```

---

# `polymorphic (C++26)`

C++ 26 introduces `polymorphic<T, A>`, a value-type which owns an object of a class derived from `T`.

As it is an owning type, `polymorphic` takes a second template argument for the allocator. If no allocator is specified, the default allocator will be used.

`polymorphic` is a value-type: only const qualified methods of the owned object can be accessed from a const-access path; copies have their own distinct, non-sliced, copies of the owned object.

`polymorphic` provides `operator->` and `operator*` to access the owned object.

---

# `protocol (Proposed addition)`

The class template `protocol<T, A>` owns an object whose interface structurally subtypes the interface of the first template argument `T`.

As it is an owning type, `protocol` takes a second template argument for the allocator. If no allocator is specified, the default allocator will be used.
Comment thread
jbcoe marked this conversation as resolved.

`protocol` is a value-type: only const qualified methods of the owned object can be accessed from a const-access path; copies have their own distinct, non-sliced, copies of the owned object.

`protocol<T>` has all of the member functions of `T` and forwards them to the owned object.

---

# `protocol_view (Proposed addition)`

The class template `protocol_view<T>` is a non-owning view on an object whose interface structurally subtypes the interface of the template argument `T`.

`protocol_view` can be cheaply copied and passed by value.

`protocol<T>` has all of the member functions of `T` and forwards them to the viewed object.

`protocol_view<const T>` can be used for const-only access to an object in a similar manner to `std::span`.

---

# Function-like objects

C++ has a plethora of function-like type-erasing class templates.

- `std::function` (C++11)
- `std::function_ref` (C++26)
- `std::move_only_function` (C++23)
- `std::copyable_function` (C++26)

Each of these can be implemented using `protocol<T>` or `protocol_view<T>` by defining a suitable `T`.

---

# Overload sets

`protocol` and `protocol_view` can be used to implement an overload set that can be used as a class member or passed as a function argument.

```c++
struct OverloadedFunction {
R1 operator()(Args1&&... args) const;
R2 operator()(Args2&&... args);
R3 operator()(Args3&&... args);
};
```

---

# Indexed containers

`protocol` and `protocol_view` can be used for indexed container access.

```c++
struct IndexedContainer<T> {
const T& operator[](size_t i) const;
T& operator[](size_t i);
size_t size() const;
};
```

---

# Implementation

A reference implementation, using an AST-based Python code generator to simulate
post-C++26 code injection, is available at <https://github.com/jbcoe/cc-protocol>.

There is an open PR to illustrate the work (currently) needed to add new types
<https://github.com/jbcoe/cc-protocol/pull/53>

---

# Future work

Future extensions to C++ reflection should allow `protocol` and `protocol_view` to be implemented without the need for a custom build step.
Loading