Skip to content

Require exact return-type matches in generated protocol concepts (1/20) - #101

Open
jbcoe wants to merge 1 commit into
tutorialsfrom
reflection/00-python-exact-match
Open

Require exact return-type matches in generated protocol concepts (1/20)#101
jbcoe wants to merge 1 commit into
tutorialsfrom
reflection/00-python-exact-match

Conversation

@jbcoe

@jbcoe jbcoe commented Jul 27, 2026

Copy link
Copy Markdown
Owner

protocol.j2's generated concepts used std::convertible_to, letting
merely-convertible return types conform; switched to std::same_as
per DRAFT.md's exact-match design. CovariantBImpl no longer conforms
to B and moves from a dispatch test to a negative static_assert.

@jbcoe jbcoe changed the title Require exact return-type matches in generated protocol concepts Require exact return-type matches in generated protocol concepts (1/20) Jul 27, 2026
@jbcoe
jbcoe marked this pull request as ready for review July 28, 2026 10:54
@jbcoe
jbcoe force-pushed the reflection/00-python-exact-match branch from 9ef84ae to 168aa68 Compare July 28, 2026 20:22
@RyanJK5

RyanJK5 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

I think whether or not this is the right direction depends on what protocol wants consistency with. For example, std::function allows convertible return types:

double f() { return 1.5; }

int main() {
    return std::function<int()>{f}(); // OK, returns 1
}

But virtual functions do not. They do allow covariant returns, though:

struct A {
    virtual A& foo() = 0;
};

struct B : A {
    B& foo() override; // OK
};

Which might be analogous to:

struct Interface {
    protocol_view<Interface> foo();
};

struct S {
    S& foo();
};

What is the rationale here for protocol's design?

@jbcoe

jbcoe commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

For now I'm keen to implement what's in the DRAFT.md and this is one area that we've been explicit about:


Relaxed structural subtyping

We require a type to have exactly matching function signatures as I to be considered a
conforming type for protocol<I>. Implicit conversions could be allowed but this might
lead to odd chains of implicit-conversion-led conformance where an object can be passed
through a sequence of protocol (or protocol_view) objects to conform to the interface
of the last protocol. Where implicit conversions are unidirectional this may lead to
undesirable or surprising behaviour.

With some suitably compelling motivation, conformance via implicit conversions could be
added to protocol in a later revision of the C++ standard without rendering existing code
ill-formed.


The strongest argument for picking this option is that it's the one that lets us change our minds later without breaking code. This approach served us well for indirect and polymorphic.

With suitable motivation, I'm happy to change this part of the design, but we should land the reflection implementation of DRAFT first. I raised #121 to track this.

protocol.j2's generated concepts used std::convertible_to<R>, letting
merely-convertible return types conform; switched to std::same_as<R>
per DRAFT.md's exact-match design. CovariantBImpl no longer conforms
to B and moves from a dispatch test to a negative static_assert.
@jbcoe
jbcoe force-pushed the reflection/00-python-exact-match branch from 168aa68 to c851094 Compare July 29, 2026 09:48
@RyanJK5

RyanJK5 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

For now I'm keen to implement what's in the DRAFT.md and this is one area that we've been explicit about:

Relaxed structural subtyping

We require a type to have exactly matching function signatures as I to be considered a conforming type for protocol<I>. Implicit conversions could be allowed but this might lead to odd chains of implicit-conversion-led conformance where an object can be passed through a sequence of protocol (or protocol_view) objects to conform to the interface of the last protocol. Where implicit conversions are unidirectional this may lead to undesirable or surprising behaviour.

With some suitably compelling motivation, conformance via implicit conversions could be added to protocol in a later revision of the C++ standard without rendering existing code ill-formed.

The strongest argument for picking this option is that it's the one that lets us change our minds later without breaking code. This approach served us well for indirect and polymorphic.

With suitable motivation, I'm happy to change this part of the design, but we should land the reflection implementation of DRAFT first. I raised #121 to track this.

That makes a lot of sense, thank you!

@RyanJK5
RyanJK5 requested review from RyanJK5 and Copilot July 31, 2026 19:04
@RyanJK5

RyanJK5 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Accidentally requested review from copilot while self-assigning, sorry about that.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens the generated protocol concepts to require exact return-type matches (aligning conformance checks with the design described in DRAFT.md) by switching from std::convertible_to<R> to std::same_as<R>. As a result, the “convertible return type” B-shaped implementation used in tests is no longer considered conforming, and the test suite is updated accordingly.

Changes:

  • Update scripts/protocol.j2 to require std::same_as<ReturnType> for generated concept return-type constraints (including void).
  • Adjust protocol_test.cc to make the previously-positive covariant/convertible-return test a negative compile-time check.
  • Update a diagnostic comment in scripts/test_concept_errors.py to reflect “exact type match” semantics.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
scripts/test_concept_errors.py Updates an explanatory comment for the “wrong return type” concept-error test case.
scripts/protocol.j2 Changes generated protocol concepts from convertible_to to same_as for return-type checking.
protocol_test.cc Replaces the runtime dispatch test for convertible return types with a negative static_assert conformance check.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/protocol.j2
{% else %}
{ t.{{ m.name }}({{ args_str }}) }{% if m.is_noexcept %} noexcept{% endif %} -> std::convertible_to<{{ m.return_type.name }}>;
{% endif %}
{ t.{{ m.name }}({{ args_str }}) }{% if m.is_noexcept %} noexcept{% endif %} -> std::same_as<{{ m.return_type.name }}>;
Comment thread protocol_test.cc
Comment on lines +509 to +510
// A B-shaped type whose methods return covariant/implicitly-convertible
// types instead of B's exact declared return types.
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.

3 participants