The draft notes that narrowing should be allowed for protocols:
// Narrowing constructor from any compatible mutable protocol_view.
template <typename Other>
protocol_view(const protocol_view<Other>& 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<A, B, C> can then be converted to a duck<A>, duck<B>, etc.
// where A, B, C are interfaces
rjk::duck<A, B, C> d{...};
rjk::duck_view<A> dv1 = d;
rjk::duck_view<B> dv2 = d;
rjk::duck_view<const C> dv3 = d;
The approach we take for this looks like:
struct vtable<A, B, C> {
const vtable<A>* to_trait_0;
const vtable<B>* to_trait_1;
const vtable<C>* to_trait_2;
// actual dispatch functions, lifecycle, etc.
};
// for single-trait vtables
struct vtable<A> {
const vtable<const A>* to_const;
};
I am currently working on a feature for the library I call "multi-trait" narrowing, or converting from a duck<A, B, C> to a duck<A, B>, duck<A, C>, 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<A, B, C> will now need to store a vtable<A, B>*, vtable<A, C>*, vtable<B, C>*, 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:
struct A {
int foo();
int bar();
int baz();
};
struct B {
int foo();
int bar();
};
protocol<A> p{...};
protocol_view<B> pv1 = p;
protocol_view<const B> 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:
struct vtable<foo, bar, baz> {
const vtable<foo>* to_func_0;
const vtable<bar>* to_func_1;
const vtable<baz>* to_func_2;
const vtable<foo, bar>* to_func_0_1;
const vtable<foo, baz>* to_func_0_2;
const vtable<bar, baz>* 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.
The draft notes that narrowing should be allowed for protocols:
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 forprotocol.duck's modelRather than accepting a single trait (same as
protocol's interface),duckaccepts a variadic number of traits. Aduck<A, B, C>can then be converted to aduck<A>,duck<B>, etc.The approach we take for this looks like:
I am currently working on a feature for the library I call "multi-trait" narrowing, or converting from a
duck<A, B, C>to aduck<A, B>,duck<A, C>, 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<A, B, C>will now need to store avtable<A, B>*,vtable<A, C>*,vtable<B, C>*, 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, sinceduckdoes not allow granular narrowing to each method.protocol's modelBringing this info to protocol, if I understood the specification correctly, the goal is to support the following:
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 forint foo(), one forint bar(), and one forint baz(). Then each one would need to store a pointer to the vtable for its constituent members. So a vtable forAmight contain: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.