jbcoe / jbcoe/cc-protocol

Narrowing protocols with reflection

Open
#98 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
11
Forks
4
Avg merge
16h 39m
Merged PRs (30d)
131

Description

The draft notes that **narrowing** should be allowed for protocols:

```c++
// Narrowing constructor from any compatible mutable protocol_view.
template
protocol_view(const protocol_view& other) noexcept;
```

This is an idea worth exploring with reflection. I would like to provide a brief overview of `duck`'s approach to this problem, and the issues we may encounter when implementing this for `protocol`.

## `duck`'s model

Rather than accepting a single trait (same as `protocol`'s interface), `duck` accepts a variadic number of traits. A `duck` can then be converted to a `duck`, `duck`, etc.

```c++
// where A, B, C are interfaces
rjk::duck d{...};
rjk::duck_view
dv1 = d;
rjk::duck_view dv2 = d;
rjk::duck_view dv3 = d;
```

The approach we take for this looks like:

```c++
struct vtable {
const vtable
* to_trait_0;
const vtable* to_trait_1;
const vtable* to_trait_2;

// actual dispatch functions, lifecycle, etc.
};

// for single-trait vtables
struct vtable
{
const vtable* to_const;
};
```

I am currently working on a feature for the library I call "multi-trait" narrowing, or converting from a `duck` to a `duck`, `duck`, or any other subset of the traits.

With the approach I'm using currently (and the best one I could think of so far) this means that a `vtable` will now need to store a `vtable*`, `vtable*`, `vtable*`, in addition to the single-trait vtable pointers. As you imagine, this could become a problem for a lot of traits. It is *not*, however, an issue for a large number of interface members, since `duck` does not allow granular narrowing to each method.

## `protocol`'s model

Bringing this info to protocol, if I understood the specification correctly, the goal is to support the following:

```c++
struct A {
int foo();
int bar();
int baz();
};

struct B {
int foo();
int bar();
};

protocol p{...};
protocol_view pv1 = p;
protocol_view pv2 = p;
```

My first thought for implementing this would be to reuse the approach from `duck`, but treating each interface member as a "trait" instead. So you would make three vtables: one for `int foo()`, one for `int bar()`, and one for `int baz()`. Then each one would need to store a pointer to the vtable for its constituent members. So a vtable for `A` might contain:

```c++
struct vtable {
const vtable* to_func_0;
const vtable* to_func_1;
const vtable* to_func_2;
const vtable* to_func_0_1;
const vtable* to_func_0_2;
const vtable* to_func_1_2;

// actual dispatch functions, lifecycle, etc.
};
```

If we are anticipating interfaces with a large number of methods, this could slow down compile time significantly. Defining one interface with many members, even if the user does not intend to narrow it at all, would cause significant overhead.

## Conclusion

I am not creating this to discourage narrowing from being included in `protocol`, since I think it is a high-value feature. Some additional research into alternate methods using reflection would be helpful, so as to potentially avoid the overhead described above. Additionally, it would be valuable to discuss this with STL implementers to see if they have access to some methods that we don't.

Contributor guide

Open the contributing guide

Research direction

Start with the issue's `duck` multi-trait narrowing model and the proposed `protocol` vtable layouts. Research alternate reflection-based approaches and relevant STL implementer techniques, as requested in the conclusion. Done means documenting a viable approach for protocol narrowing and the overhead or trade-offs it introduces.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.