Skip to content

[SPLIT BEFORE MERGING] Implementation of protocol using GCC 16's C++26 reflection#95

Draft
jbcoe wants to merge 9 commits into
mainfrom
reflection-implementation
Draft

[SPLIT BEFORE MERGING] Implementation of protocol using GCC 16's C++26 reflection#95
jbcoe wants to merge 9 commits into
mainfrom
reflection-implementation

Conversation

@jbcoe

@jbcoe jbcoe commented Jul 19, 2026

Copy link
Copy Markdown
Owner

SPLIT BEFORE MERGING

Closes #94

Based on Duck by @RyanJK5

I anticipate that this will need significantly re-writing.

DRAFT.md is not yet updated and will be amended in a follow-up PR.

@jbcoe
jbcoe requested review from Twon, nbx8 and philipcraig July 19, 2026 19:21
@jbcoe
jbcoe force-pushed the reflection-implementation branch 2 times, most recently from fcafe99 to 61c971b Compare July 20, 2026 20:56
@jbcoe
jbcoe force-pushed the reflection-implementation branch from 993309d to 1ce2b8b Compare July 20, 2026 21:50
@jbcoe
jbcoe force-pushed the reflection-implementation branch from 64301c2 to 0653e76 Compare July 20, 2026 23:34
@jbcoe
jbcoe requested a review from RyanJK5 July 21, 2026 19:08
@jbcoe
jbcoe marked this pull request as ready for review July 21, 2026 19:19
Comment thread protocol_reflection.h Outdated

template <typename Interface, typename Owner, bool ConstOnly,
bool ForceConstCall>
struct named_forwarders {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand correctly, it seems that named_forwarders::type will end up looking something like this:

struct type {
    [[no_unique_address]] forwarding_call<...> foo;
    [[no_unique_address]] forwarding_call<...> bar;
    // etc.
};

And then bar will static_cast to void*, then to policy / policy_view.

I believe this approach is technically UB. It assumes that foo, bar, etc. all occupy the same memory address, but no_unique_address does not necessarily guarantee that.

The approach duck uses sidestepped this by making each one the first data member of a base class:

struct wrapper_1 {
    [[no_unique_address]] forwarding_call<...> foo;
};

struct wrapper_2 {
    [[no_unique_address]] forwarding_call<...> bar;
};

struct type : wrapper_1, wrapper_2 {};

Which then allows casting from foo and bar to wrapper_1 and wrapper_2 via void* or reinterpret_cast, and then safely static_cast-ing to policy / policy_view.

But again, this is all really technicalities, and especially since this is a reference implementation, I'm not sure it is a necessary change. I just wanted to mention it here.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. We’ve no reason to be different to Duck and should do exactly the same wherever possible. I’ll rework this.

Comment thread protocol_reflection.h Outdated

// Identifier-safe spelling for an operator kind, mirroring the intent of
// mangle_identifier in scripts/generate_protocol.py.
consteval std::string_view operator_spelling(std::meta::operators kind) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to write a simple enum_to_string helper for this now, rather than the big switch-case. You could copy the implementation from the C++26 reflection proposal.

Comment thread protocol_reflection.h Outdated
explicit implementation_candidate_call(SelfPointer self) : self(self) {}

ReturnType operator()(ParameterTypes... parameters) const {
return self->[:Candidate:](std::forward<ParameterTypes>(parameters)...);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a glance, I believe this may be discarding reference qualification, so rvalue-qualified members may not be found by overload resolution.

@jbcoe
jbcoe marked this pull request as draft July 24, 2026 14:40
@jbcoe

jbcoe commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

While this works, committing it as one large PR feels like a mistake. I'll build it up incrementally in smaller easy-to-digest pieces.

@RyanJK5

RyanJK5 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Clarifying question--do lvalue/rvalue qualified interface members hold any significance? I know protocol propagates const, but is the underlying type an lvalue regardless of if protocol is an lvalue or rvalue?

@jbcoe

jbcoe commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Clarifying question--do lvalue/rvalue qualified interface members hold any significance? I know protocol propagates const, but is the underlying type an lvalue regardless of if protocol is an lvalue or rvalue?

I'm not sure. We've not thought about this in DRAFT. I think that we should probably treat the underlying type as an rvalue when protocol is an rvalue. This will need some thought and work.

@jbcoe jbcoe changed the title Implementation of protocol using GCC 16's C++26 reflection [SPLIT BEFORE MERGING] Implementation of protocol using GCC 16's C++26 reflection Jul 24, 2026
@RyanJK5

RyanJK5 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Clarifying question--do lvalue/rvalue qualified interface members hold any significance? I know protocol propagates const, but is the underlying type an lvalue regardless of if protocol is an lvalue or rvalue?

I'm not sure. We've not thought about this in DRAFT. I think that we should probably treat the underlying type as an rvalue when protocol is an rvalue. This will need some thought and work.

Got it. For reference, duck does propagate rvalue-ness, but it brings some considerable implementation complexity. I can open an issue if that's helpful.

@jbcoe

jbcoe commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Clarifying question--do lvalue/rvalue qualified interface members hold any significance? I know protocol propagates const, but is the underlying type an lvalue regardless of if protocol is an lvalue or rvalue?

I'm not sure. We've not thought about this in DRAFT. I think that we should probably treat the underlying type as an rvalue when protocol is an rvalue. This will need some thought and work.

Got it. For reference, duck does propagate rvalue-ness, but it brings some considerable implementation complexity. I can open an issue if that's helpful.

Please do. This feels like the right approach. We can incorporate the complexity but it's an extension to what's currently in DRAFT so feels better as follow-up work.

@Twon

Twon commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I've had a quick review of this for now, but I think this definitely could do with being broken down into constituent parts instead of being in one big header. Anyone coming to this will have a much easier time if the reflection header is split into files that reduce the scope and indicate the structure of the code.

@RyanJK5

RyanJK5 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I've had a quick review of this for now, but I think this definitely could do with being broken down into constituent parts instead of being in one big header. Anyone coming to this will have a much easier time if the reflection header is split into files that reduce the scope and indicate the structure of the code.

I agree. For instance, the generic reflection utilities, the vtable generation, and the wrappers that call into the vtable could all be separate files. There's probably even finer-grained lines that can be drawn if that still produces some monoliths.

@jbcoe
jbcoe removed request for Twon, nbx8 and philipcraig July 25, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider consolidation with https://ryanjk5.github.io/posts/rjk-duck/

3 participants