From 0c41986ef02746c142e7512a88d3b731e55200e0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 17:27:18 +0000
Subject: [PATCH 1/3] Initial plan
From a25c08de03bbdb645baa95c56008646256ae5e37 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 17:41:02 +0000
Subject: [PATCH 2/3] Add stub implementations for interface member functions
using vanishing this pointer trick
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
CMakeLists.txt | 33 +++
cmake/xyz_add_test.cmake | 2 +-
reflection/CMakeLists.txt | 17 ++
reflection/protocol.h | 376 ++++++++++++++++++++++++++++++
reflection/protocol_test.cc | 446 ++++++++++++++++++++++++++++++++++++
scripts/cmake.py | 20 +-
6 files changed, 892 insertions(+), 2 deletions(-)
create mode 100644 reflection/CMakeLists.txt
create mode 100644 reflection/protocol.h
create mode 100644 reflection/protocol_test.cc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0626252..3a63ea8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,35 @@ option(ENABLE_UBSAN "Enable Undefined Behaviour Sanitizer" OFF)
option(ENABLE_TSAN "Enable Thread Sanitizer" OFF)
option(ENABLE_MSAN "Enable Memory Sanitizer" OFF)
+option(
+ XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION
+ "Also build and test the C++26-reflection-based implementation of protocol, \
+which requires a compiler with C++26 P2996 reflection support \
+(GCC 16+ with -freflection)."
+ OFF)
+
+if(XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION)
+ include(CheckCXXSourceCompiles)
+ set(CMAKE_REQUIRED_FLAGS "-std=c++26 -freflection")
+ check_cxx_source_compiles(
+ "
+ #include
+ constexpr std::meta::info reflection_of_int = ^^int;
+ int main() {}
+ "
+ XYZ_PROTOCOL_REFLECTION_SUPPORTED)
+ unset(CMAKE_REQUIRED_FLAGS)
+ if(NOT XYZ_PROTOCOL_REFLECTION_SUPPORTED)
+ message(
+ FATAL_ERROR
+ "XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION is ON but the compiler "
+ "(${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}) does not "
+ "accept '-std=c++26 -freflection'. C++26 reflection currently "
+ "requires GCC 16 or newer; configure with e.g. "
+ "CXX=g++-16 CC=gcc-16 and a separate build directory (-B).")
+ endif()
+endif()
+
if(ENABLE_ASAN OR ENABLE_UBSAN OR ENABLE_TSAN OR ENABLE_MSAN)
set(ENABLE_SANITIZERS ON)
endif()
@@ -171,6 +200,10 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
--flags=-I${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()
+
+ if(XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION)
+ add_subdirectory(reflection)
+ endif()
endif(${BUILD_TESTING})
endif()
diff --git a/cmake/xyz_add_test.cmake b/cmake/xyz_add_test.cmake
index 00cf265..ff9f4a6 100644
--- a/cmake/xyz_add_test.cmake
+++ b/cmake/xyz_add_test.cmake
@@ -50,7 +50,7 @@ function(xyz_add_test)
if(NOT XYZ_VERSION)
set(XYZ_VERSION 20)
else()
- set(VALID_TARGET_VERSIONS 11 14 17 20 23)
+ set(VALID_TARGET_VERSIONS 11 14 17 20 23 26)
list(FIND VALID_TARGET_VERSIONS ${XYZ_VERSION} index)
if(index EQUAL -1)
message(FATAL_ERROR "TYPE must be one of <${VALID_TARGET_VERSIONS}>")
diff --git a/reflection/CMakeLists.txt b/reflection/CMakeLists.txt
new file mode 100644
index 0000000..59a4420
--- /dev/null
+++ b/reflection/CMakeLists.txt
@@ -0,0 +1,17 @@
+xyz_add_library(
+ NAME reflection_protocol
+ ALIAS xyz_protocol::reflection_protocol)
+target_sources(
+ reflection_protocol INTERFACE
+ $)
+
+xyz_add_test(
+ NAME
+ reflection_protocol_test
+ VERSION
+ 26
+ LINK_LIBRARIES
+ xyz_protocol::reflection_protocol
+ FILES
+ protocol_test.cc)
+target_compile_options(reflection_protocol_test PRIVATE -freflection)
diff --git a/reflection/protocol.h b/reflection/protocol.h
new file mode 100644
index 0000000..6713fdc
--- /dev/null
+++ b/reflection/protocol.h
@@ -0,0 +1,376 @@
+/* Copyright (c) 2025 The XYZ Protocol Authors. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+==============================================================================*/
+#ifndef XYZ_REFLECTION_PROTOCOL_H_
+#define XYZ_REFLECTION_PROTOCOL_H_
+
+// A C++26-reflection-based implementation of protocol and protocol_view.
+//
+// Member function stubs are synthesised at compile time for every public
+// non-special member function declared in the Interface type. The stubs
+// are attached to protocol and protocol_view through data members that
+// provide ordinary member-function call syntax via the "vanishing this
+// pointer" technique described in tutorials/2_vanishing_this_pointer.cc
+// (section 5): each per-method wrapper sits as the sole member of a
+// dedicated base struct; the wrapper's operator() recovers the enclosing
+// base address through a static_cast and hands it to the derived class.
+//
+// The stubs are intentionally unimplemented beyond the signature: they
+// call std::unreachable() so that the type-system plumbing can be
+// developed before the vtable dispatch layer exists.
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace xyz::reflection {
+
+template
+class protocol;
+
+template
+class protocol_view;
+
+template
+struct is_protocol : std::false_type {};
+
+template
+struct is_protocol> : std::true_type {};
+
+template
+struct is_protocol_view : std::false_type {};
+
+template
+struct is_protocol_view> : std::true_type {};
+
+template
+concept is_neither_protocol_nor_protocol_view =
+ !is_protocol>::value &&
+ !is_protocol_view>::value;
+
+namespace detail {
+
+// Returns true if the concrete member function (rhs) satisfies the interface
+// member function (lhs) with respect to: name, return type, parameter types,
+// constness, ref-qualifier, and noexcept. For noexcept, the rule is:
+// - If the interface requires noexcept, the concrete must also be noexcept.
+// - If the interface does not require noexcept, the concrete may be either
+// (a noexcept concrete is still conformant with a non-noexcept interface).
+// Ref-qualifiers (none, &, &&) must match exactly.
+consteval bool member_function_signatures_match(std::meta::info lhs,
+ std::meta::info rhs) {
+ if (!has_identifier(lhs) || !has_identifier(rhs)) return false;
+ if (identifier_of(lhs) != identifier_of(rhs)) return false;
+ if (is_const(lhs) != is_const(rhs)) return false;
+ if (is_lvalue_reference_qualified(lhs) != is_lvalue_reference_qualified(rhs))
+ return false;
+ if (is_rvalue_reference_qualified(lhs) != is_rvalue_reference_qualified(rhs))
+ return false;
+ if (is_noexcept(lhs) && !is_noexcept(rhs)) return false;
+ if (dealias(return_type_of(lhs)) != dealias(return_type_of(rhs)))
+ return false;
+ std::vector lhs_params = parameters_of(lhs);
+ std::vector rhs_params = parameters_of(rhs);
+ if (lhs_params.size() != rhs_params.size()) return false;
+ for (std::size_t i = 0; i < lhs_params.size(); ++i) {
+ if (dealias(type_of(lhs_params[i])) != dealias(type_of(rhs_params[i])))
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+// Vanishing-this-pointer thunks for synthesised member stubs.
+//
+// Each thunk type carries a single operator() whose signature mirrors one
+// method of the Interface type. The thunk lives as a [[no_unique_address]]
+// data member inside a per-method base struct (section 5 of the tutorial);
+// operator() recovers the base struct's address from its own address (valid
+// because the thunk is the sole, offset-zero member of that base) and then
+// casts further to the protocol or protocol_view that inherits from it.
+//
+// The four partial specialisations below cover the four combinations of
+// const/non-const × noexcept/potentially-throwing that an interface method
+// can declare.
+// ---------------------------------------------------------------------------
+
+// Helper alias used by method_thunk_specs to build function-pointer types
+// that encode the method's return type and parameter types.
+template
+using fn_ptr_t = R (*)(Args...);
+
+// Non-const, potentially-throwing method thunk.
+// OwnerBase is the dedicated base struct holding this thunk; the derived
+// class (protocol or protocol_view) inherits from OwnerBase.
+template
+struct method_thunk_mutable;
+
+template
+struct method_thunk_mutable {
+ // Provides member-function call syntax. Recovers the OwnerBase pointer
+ // through the vanishing-this-pointer cast, then calls the protocol's
+ // stored vtable entry (not yet implemented: stub calls std::unreachable).
+ R operator()(Args... /*args*/) {
+ // Vanishing this pointer: this thunk is at offset 0 of OwnerBase.
+ [[maybe_unused]] auto* base =
+ static_cast(static_cast(this));
+ std::unreachable(); // stub — vtable dispatch not yet implemented
+ }
+};
+
+// Const, potentially-throwing method thunk.
+template
+struct method_thunk_const;
+
+template
+struct method_thunk_const {
+ R operator()(Args... /*args*/) const {
+ [[maybe_unused]] const auto* base =
+ static_cast(static_cast(this));
+ std::unreachable(); // stub — vtable dispatch not yet implemented
+ }
+};
+
+// Non-const, noexcept method thunk.
+template
+struct method_thunk_mutable_noexcept;
+
+template
+struct method_thunk_mutable_noexcept {
+ R operator()(Args... /*args*/) noexcept {
+ [[maybe_unused]] auto* base =
+ static_cast(static_cast(this));
+ std::unreachable(); // stub — vtable dispatch not yet implemented
+ }
+};
+
+// Const, noexcept method thunk.
+template
+struct method_thunk_const_noexcept;
+
+template
+struct method_thunk_const_noexcept {
+ R operator()(Args... /*args*/) const noexcept {
+ [[maybe_unused]] const auto* base =
+ static_cast(static_cast(this));
+ std::unreachable(); // stub — vtable dispatch not yet implemented
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Compile-time helper: build a list of data_member_spec values, one per
+// public non-special member function of interface_type.
+//
+// Each spec names the data member after the interface method (giving the
+// p.method_name(args) call syntax) and sets its type to the matching thunk
+// template specialisation. [[no_unique_address]] is requested so that
+// thunks for methods returning void add no storage overhead.
+//
+// Methods that share a name (overloads) are each given their own data
+// member. Because C++ disallows two data members with the same name inside
+// the same class, overloaded methods whose names collide are skipped after
+// the first occurrence and excluded from the synthesised bases. A future
+// revision can handle overloads by encoding the parameter types into the
+// member name (see tutorials/3_reflection.cc section 5).
+// ---------------------------------------------------------------------------
+consteval std::vector method_thunk_specs(
+ std::meta::info interface_type, std::meta::info owner_base_type) {
+ std::vector specs;
+ std::vector seen_names;
+
+ for (std::meta::info member :
+ members_of(interface_type,
+ std::meta::access_context::unprivileged())) {
+ if (!is_function(member)) continue;
+ if (is_special_member_function(member)) continue;
+ if (!has_identifier(member)) continue;
+
+ std::string_view name = identifier_of(member);
+
+ // Skip overloads: only the first declaration with a given name is
+ // included. See the comment above for why.
+ bool already_seen = false;
+ for (std::string_view seen : seen_names) {
+ if (seen == name) {
+ already_seen = true;
+ break;
+ }
+ }
+ if (already_seen) continue;
+ seen_names.push_back(name);
+
+ // Build the function-pointer type R(*)(Args...) from the method's
+ // return type and parameter types.
+ std::vector fn_args{dealias(return_type_of(member))};
+ for (std::meta::info parameter : parameters_of(member)) {
+ fn_args.push_back(dealias(type_of(parameter)));
+ }
+ std::meta::info fn_ptr_type = substitute(^^fn_ptr_t, fn_args);
+
+ // Choose the thunk template based on the method's constness and
+ // noexcept specification.
+ std::meta::info thunk_template;
+ if (is_const(member) && is_noexcept(member)) {
+ thunk_template = ^^method_thunk_const_noexcept;
+ } else if (is_const(member)) {
+ thunk_template = ^^method_thunk_const;
+ } else if (is_noexcept(member)) {
+ thunk_template = ^^method_thunk_mutable_noexcept;
+ } else {
+ thunk_template = ^^method_thunk_mutable;
+ }
+
+ std::meta::info thunk_type =
+ substitute(thunk_template, {fn_ptr_type, owner_base_type});
+
+ specs.push_back(data_member_spec(
+ thunk_type,
+ std::meta::data_member_options{.name = name, .no_unique_address = true}));
+ }
+ return specs;
+}
+
+} // namespace detail
+
+// ---------------------------------------------------------------------------
+// Returns true at compile time if every public member function declared in
+// Interface is present in Concrete with a matching signature (name, return
+// type, parameter types, constness, ref-qualifier, and noexcept).
+// ---------------------------------------------------------------------------
+template
+consteval bool conforms_to() {
+ for (std::meta::info interface_member :
+ members_of(^^Interface, std::meta::access_context::unprivileged())) {
+ if (!is_function(interface_member)) continue;
+ if (!has_identifier(interface_member)) continue;
+ bool found = false;
+ for (std::meta::info concrete_member :
+ members_of(^^Concrete, std::meta::access_context::unprivileged())) {
+ if (!is_function(concrete_member)) continue;
+ if (!has_identifier(concrete_member)) continue;
+ if (detail::member_function_signatures_match(interface_member,
+ concrete_member)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) return false;
+ }
+ return true;
+}
+
+// Variable template for use in requires clauses.
+template
+inline constexpr bool conforms_to_v = conforms_to();
+
+// ---------------------------------------------------------------------------
+// Synthesised member-stub bases.
+//
+// protocol_member_stubs and protocol_view_member_stubs are
+// incomplete classes that are completed by a consteval block inside the
+// protocol / protocol_view class template body. Each block calls
+// define_aggregate to inject one [[no_unique_address]] thunk data member
+// per public non-special member function of T, giving protocol and
+// protocol_view the member-function call syntax described at the top of
+// this file.
+// ---------------------------------------------------------------------------
+
+// Base injected into protocol.
+template
+struct protocol_member_stubs;
+
+// Base injected into protocol_view.
+template
+struct protocol_view_member_stubs;
+
+// ---------------------------------------------------------------------------
+// protocol
+// ---------------------------------------------------------------------------
+template >
+class protocol : public protocol_member_stubs {
+ // Synthesise the member stubs for this specialisation. The consteval
+ // block runs during translation when the class template is instantiated.
+ // define_aggregate completes protocol_member_stubs by injecting one
+ // thunk data member per method of T. The thunk's OwnerBase parameter is
+ // protocol_member_stubs itself, matching the vanishing-this-pointer
+ // requirement that the thunk is the offset-zero member of its base.
+ consteval {
+ define_aggregate(^^protocol_member_stubs,
+ detail::method_thunk_specs(^^T, ^^protocol_member_stubs));
+ }
+
+ public:
+ // Special member functions.
+ protocol() = delete;
+
+ protocol(const protocol&)
+ requires std::is_copy_constructible_v;
+
+ protocol(protocol&&)
+ requires std::is_move_constructible_v;
+
+ protocol& operator=(const protocol&)
+ requires std::is_copy_assignable_v;
+
+ protocol& operator=(protocol&&)
+ requires std::is_move_assignable_v;
+
+ ~protocol(); // Unconstrained.
+
+ // Construct from any type U that conforms to the Interface T.
+ template
+ requires conforms_to_v> &&
+ is_neither_protocol_nor_protocol_view
+ explicit protocol(U&& value);
+};
+
+// ---------------------------------------------------------------------------
+// protocol_view
+// ---------------------------------------------------------------------------
+template
+class protocol_view : public protocol_view_member_stubs {
+ // Synthesise the member stubs for this specialisation.
+ consteval {
+ define_aggregate(
+ ^^protocol_view_member_stubs,
+ detail::method_thunk_specs(^^T, ^^protocol_view_member_stubs));
+ }
+
+ public:
+ // Special member functions.
+ protocol_view() = delete;
+ protocol_view(const protocol_view&) = default;
+ protocol_view(protocol_view&&) = default;
+ protocol_view& operator=(const protocol_view&) = default;
+ protocol_view& operator=(protocol_view&&) = default;
+ ~protocol_view() = default;
+
+ // Construct from any type U that conforms to the Interface T.
+ template
+ requires conforms_to_v> &&
+ is_neither_protocol_nor_protocol_view
+ explicit protocol_view(const U& object);
+};
+
+} // namespace xyz::reflection
+#endif // XYZ_REFLECTION_PROTOCOL_H_
diff --git a/reflection/protocol_test.cc b/reflection/protocol_test.cc
new file mode 100644
index 0000000..f62848a
--- /dev/null
+++ b/reflection/protocol_test.cc
@@ -0,0 +1,446 @@
+// Tests for the C++26-reflection-based implementation of protocol and
+// protocol_view.
+//
+// Specifically covers:
+// - Special member function availability.
+// - Constructability from conforming/non-conforming types.
+// - Conformance checking via conforms_to<>.
+// - Member function stub invocability via the vanishing-this-pointer
+// synthesised members.
+
+#include "protocol.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+using xyz::reflection::conforms_to;
+using xyz::reflection::protocol;
+using xyz::reflection::protocol_view;
+
+namespace {
+
+// ---------------------------------------------------------------------------
+// Special member function tests (protocol_view).
+// ---------------------------------------------------------------------------
+
+TEST(ReflectionProtocolViewTest, CheckSpecialMembers) {
+ // protocol_view is not default-constructible but can be copied, moved,
+ // assigned, move assigned and destroyed.
+ struct A {};
+
+ static_assert(!std::is_default_constructible_v>);
+ static_assert(std::is_copy_constructible_v>);
+ static_assert(std::is_move_constructible_v>);
+ static_assert(std::is_copy_assignable_v>);
+ static_assert(std::is_move_assignable_v>);
+ static_assert(std::is_destructible_v>);
+}
+
+// ---------------------------------------------------------------------------
+// Special member function tests (protocol).
+// ---------------------------------------------------------------------------
+
+TEST(ReflectionProtocolTest, CheckSpecialMembers) {
+ // protocol is not default-constructible but can be copied, moved, assigned
+ // and move assigned if the underlying type can be.
+ struct A {};
+
+ static_assert(!std::is_default_constructible_v>);
+ static_assert(std::is_copy_constructible_v>);
+ static_assert(std::is_move_constructible_v>);
+ static_assert(std::is_copy_assignable_v>);
+ static_assert(std::is_move_assignable_v>);
+}
+
+// ---------------------------------------------------------------------------
+// Constructability tests.
+// ---------------------------------------------------------------------------
+
+TEST(ReflectionProtocolTest, IsConstructibleFromConformingType) {
+ struct Interface {
+ std::string_view name() const noexcept;
+ };
+
+ struct Conforming {
+ std::string_view name() const noexcept { return "conforming"; }
+ };
+
+ struct NonConforming {};
+
+ static_assert(std::is_constructible_v, Conforming>);
+ static_assert(!std::is_constructible_v, NonConforming>);
+}
+
+TEST(ReflectionProtocolViewTest, IsConstructibleFromConformingType) {
+ struct Interface {
+ std::string_view name() const noexcept;
+ };
+
+ struct Conforming {
+ std::string_view name() const noexcept { return "conforming"; }
+ };
+
+ struct NonConforming {};
+
+ static_assert(std::is_constructible_v, Conforming>);
+ static_assert(
+ !std::is_constructible_v, NonConforming>);
+}
+
+// ---------------------------------------------------------------------------
+// Conformance check tests.
+// ---------------------------------------------------------------------------
+
+TEST(ConformsToTest, EmptyInterfaceIsAlwaysSatisfied) {
+ struct EmptyInterface {};
+
+ struct Concrete {};
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, ConcreteTypeConformsWhenAllMethodsMatch) {
+ struct Interface {
+ std::string_view name() const noexcept;
+ int count();
+ };
+
+ struct Concrete {
+ std::string_view name() const noexcept { return "test"; }
+
+ int count() { return 42; }
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, ConcreteTypeConformsWithExtraMethodsPresent) {
+ struct Interface {
+ void process();
+ };
+
+ struct ConcreteWithExtra {
+ void process() {}
+
+ void extra_method() {}
+
+ int another() const { return 0; }
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, ConcreteTypeMissingMethodDoesNotConform) {
+ struct Interface {
+ void foo();
+ void bar();
+ };
+
+ struct MissingBar {
+ void foo() {}
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, WrongConstnessDoesNotConform) {
+ struct Interface {
+ int value() const;
+ };
+
+ struct NonConst {
+ int value(); // not const — does not match the interface
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, ConstMethodNotSatisfiedByNonConstDoesNotConform) {
+ struct Interface {
+ void process() const;
+ };
+
+ struct NonConstProcess {
+ void process() {} // missing const qualifier
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, WrongReturnTypeDoesNotConform) {
+ struct Interface {
+ int compute();
+ };
+
+ struct WrongReturn {
+ double compute() { return 0.0; }
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, WrongParameterTypeDoesNotConform) {
+ struct Interface {
+ void process(int value);
+ };
+
+ struct WrongParam {
+ void process(double value) {}
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, WrongParameterCountDoesNotConform) {
+ struct Interface {
+ void process(int a, int b);
+ };
+
+ struct WrongArity {
+ void process(int a) {}
+ };
+
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, MultipleParametersMatchCorrectly) {
+ struct Interface {
+ void write(int length, double value);
+ };
+
+ struct Concrete {
+ void write(int length, double value) {}
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, ConcreteTypeConformsForTypicalInterfaceA) {
+ struct InterfaceA {
+ std::string_view name() const noexcept;
+ int count();
+ };
+
+ struct ConcreteA {
+ std::string_view name() const noexcept { return "concrete"; }
+
+ int count() { return 1; }
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, ConcreteTypeConformsForTypicalInterfaceB) {
+ struct InterfaceB {
+ void process(const std::string& input);
+ std::vector get_results() const;
+ bool is_ready() const;
+ };
+
+ struct ConcreteB {
+ void process(const std::string& input) {}
+
+ std::vector get_results() const { return {}; }
+
+ bool is_ready() const { return true; }
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, NoexceptInterfaceRequiresNoexceptConcrete) {
+ struct Interface {
+ void f() noexcept;
+ };
+
+ struct Conforming {
+ void f() noexcept {}
+ };
+
+ struct NonNoexcept {
+ void f() {}
+ };
+
+ static_assert(conforms_to());
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, NonNoexceptInterfaceAcceptsNoexceptConcrete) {
+ struct Interface {
+ void f();
+ };
+
+ struct NoexceptConcrete {
+ void f() noexcept {}
+ };
+
+ static_assert(conforms_to());
+}
+
+TEST(ConformsToTest, LvalueRefQualifierMustMatch) {
+ struct Interface {
+ void f() &;
+ };
+
+ struct Conforming {
+ void f() & {}
+ };
+
+ struct UnqualifiedConcrete {
+ void f() {}
+ };
+
+ struct RvalueRefConcrete {
+ void f() && {}
+ };
+
+ static_assert(conforms_to());
+ static_assert(!conforms_to());
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, RvalueRefQualifierMustMatch) {
+ struct Interface {
+ void f() &&;
+ };
+
+ struct Conforming {
+ void f() && {}
+ };
+
+ struct UnqualifiedConcrete {
+ void f() {}
+ };
+
+ struct LvalueRefConcrete {
+ void f() & {}
+ };
+
+ static_assert(conforms_to());
+ static_assert(!conforms_to());
+ static_assert(!conforms_to());
+}
+
+TEST(ConformsToTest, UnqualifiedInterfaceDoesNotMatchRefQualifiedConcrete) {
+ struct Interface {
+ void f();
+ };
+
+ struct LvalueRefConcrete {
+ void f() & {}
+ };
+
+ struct RvalueRefConcrete {
+ void f() && {}
+ };
+
+ static_assert(!conforms_to());
+ static_assert(!conforms_to());
+}
+
+// ---------------------------------------------------------------------------
+// Member function stub invocability tests.
+//
+// These tests verify that protocol and protocol_view
+// expose member function stubs with the correct signatures, as synthesised
+// by the vanishing-this-pointer approach in protocol.h.
+//
+// The stubs always call std::unreachable() internally, so the tests only
+// check that the call expressions compile; they do not invoke the stubs at
+// runtime (which would be undefined behaviour before the vtable layer
+// exists).
+// ---------------------------------------------------------------------------
+
+// Verify that a protocol with a single const method exposes that method.
+TEST(MemberStubTest, ConstMethodIsSynthesisedOnProtocol) {
+ struct Interface {
+ int get_value() const;
+ };
+
+ // The synthesised member must be accessible and have the right signature.
+ // Use decltype to confirm the return type without executing the stub.
+ static_assert(
+ std::is_same_v>().get_value()),
+ int>);
+}
+
+// Verify that a protocol with a non-const method exposes that method.
+TEST(MemberStubTest, MutableMethodIsSynthesisedOnProtocol) {
+ struct Interface {
+ void update(int value);
+ };
+
+ static_assert(
+ std::is_same_v>().update(0)),
+ void>);
+}
+
+// Verify that a noexcept method stub is marked noexcept on protocol.
+TEST(MemberStubTest, NoexceptMethodStubIsNoexceptOnProtocol) {
+ struct Interface {
+ double compute(double input) noexcept;
+ };
+
+ static_assert(
+ noexcept(std::declval>().compute(0.0)));
+}
+
+// Verify that a const noexcept method stub is noexcept on protocol.
+TEST(MemberStubTest, ConstNoexceptMethodStubIsNoexceptOnProtocol) {
+ struct Interface {
+ std::string_view name() const noexcept;
+ };
+
+ static_assert(
+ noexcept(std::declval>().name()));
+}
+
+// Verify that a protocol_view exposes a const method stub.
+TEST(MemberStubTest, ConstMethodIsSynthesisedOnProtocolView) {
+ struct Interface {
+ int get_value() const;
+ };
+
+ static_assert(
+ std::is_same_v>().get_value()),
+ int>);
+}
+
+// Verify that a protocol_view exposes a non-const method stub.
+TEST(MemberStubTest, MutableMethodIsSynthesisedOnProtocolView) {
+ struct Interface {
+ void update(int value);
+ };
+
+ static_assert(
+ std::is_same_v>().update(0)),
+ void>);
+}
+
+// Verify multi-parameter method stubs compile with the correct signature.
+TEST(MemberStubTest, MultiParameterMethodIsSynthesised) {
+ struct Interface {
+ int add(int a, int b) const;
+ };
+
+ static_assert(
+ std::is_same_v<
+ decltype(std::declval>().add(1, 2)), int>);
+}
+
+// Verify that a method returning void is synthesised correctly.
+TEST(MemberStubTest, VoidReturnMethodIsSynthesised) {
+ struct Interface {
+ void reset();
+ };
+
+ static_assert(
+ std::is_same_v>().reset()),
+ void>);
+}
+
+} // namespace
diff --git a/scripts/cmake.py b/scripts/cmake.py
index ecfca13..888ab37 100644
--- a/scripts/cmake.py
+++ b/scripts/cmake.py
@@ -2,6 +2,7 @@
"""CMake helper script for building and testing the project."""
import argparse
+import os
import subprocess
from typing import Any
@@ -37,6 +38,12 @@ def main() -> None:
)
parser.add_argument("--tsan", action="store_true", help="Enable Thread Sanitizer")
parser.add_argument("--msan", action="store_true", help="Enable Memory Sanitizer")
+ parser.add_argument(
+ "--reflection",
+ action="store_true",
+ help="Build and test the C++26 reflection-based implementation (requires a "
+ "P2996 reflection compiler, e.g. CXX=g++-16 CC=gcc-16)",
+ )
parser.add_argument("-B", "--build-dir", help="Build directory")
parser.add_argument(
"--clean", action="store_true", help="Fresh configuration and clean-first build"
@@ -74,6 +81,8 @@ def log(msg: Any) -> None:
f"-DENABLE_UBSAN={'ON' if args.ubsan else 'OFF'}",
f"-DENABLE_TSAN={'ON' if args.tsan else 'OFF'}",
f"-DENABLE_MSAN={'ON' if args.msan else 'OFF'}",
+ "-DXYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION="
+ + ("ON" if args.reflection else "OFF"),
]
if args.build_dir:
configure_args.extend(["-B", args.build_dir])
@@ -82,8 +91,17 @@ def log(msg: Any) -> None:
configure_args.extend(extra)
+ # A P2996 reflection compiler is required to configure with
+ # XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION=ON. CMake only reads CXX/CC
+ # from the environment, not from -D cache variables, so set them here
+ # rather than as configure_args.
+ configure_env = os.environ.copy()
+ if args.reflection:
+ configure_env["CXX"] = "g++-16"
+ configure_env["CC"] = "gcc-16"
+
log(f"Running: {' '.join(configure_args)}")
- subprocess.check_call(configure_args)
+ subprocess.check_call(configure_args, env=configure_env)
# Build step (required for build, test, benchmark)
build_args = ["cmake", "--build"]
From 4b20c02ebf621ac0d238ecc3e88667ab36390c9b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 17:43:52 +0000
Subject: [PATCH 3/3] Address code review: fix conforms_to special members,
cmake.py environment handling, and concept name
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 7 ++++---
scripts/cmake.py | 10 ++++++----
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index 6713fdc..a6c0e8c 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -64,7 +64,7 @@ template
struct is_protocol_view> : std::true_type {};
template
-concept is_neither_protocol_nor_protocol_view =
+concept not_protocol_or_view =
!is_protocol>::value &&
!is_protocol_view>::value;
@@ -263,6 +263,7 @@ consteval bool conforms_to() {
members_of(^^Interface, std::meta::access_context::unprivileged())) {
if (!is_function(interface_member)) continue;
if (!has_identifier(interface_member)) continue;
+ if (is_special_member_function(interface_member)) continue;
bool found = false;
for (std::meta::info concrete_member :
members_of(^^Concrete, std::meta::access_context::unprivileged())) {
@@ -340,7 +341,7 @@ class protocol : public protocol_member_stubs {
// Construct from any type U that conforms to the Interface T.
template
requires conforms_to_v> &&
- is_neither_protocol_nor_protocol_view
+ not_protocol_or_view
explicit protocol(U&& value);
};
@@ -368,7 +369,7 @@ class protocol_view : public protocol_view_member_stubs {
// Construct from any type U that conforms to the Interface T.
template
requires conforms_to_v> &&
- is_neither_protocol_nor_protocol_view
+ not_protocol_or_view
explicit protocol_view(const U& object);
};
diff --git a/scripts/cmake.py b/scripts/cmake.py
index 888ab37..9ccac60 100644
--- a/scripts/cmake.py
+++ b/scripts/cmake.py
@@ -42,7 +42,8 @@ def main() -> None:
"--reflection",
action="store_true",
help="Build and test the C++26 reflection-based implementation (requires a "
- "P2996 reflection compiler, e.g. CXX=g++-16 CC=gcc-16)",
+ "P2996 reflection compiler). Defaults to g++-16/gcc-16 unless CXX/CC are "
+ "already set in the environment.",
)
parser.add_argument("-B", "--build-dir", help="Build directory")
parser.add_argument(
@@ -94,11 +95,12 @@ def log(msg: Any) -> None:
# A P2996 reflection compiler is required to configure with
# XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION=ON. CMake only reads CXX/CC
# from the environment, not from -D cache variables, so set them here
- # rather than as configure_args.
+ # rather than as configure_args. Use setdefault so that a CXX/CC already
+ # present in the caller's environment takes precedence over the defaults.
configure_env = os.environ.copy()
if args.reflection:
- configure_env["CXX"] = "g++-16"
- configure_env["CC"] = "gcc-16"
+ configure_env.setdefault("CXX", "g++-16")
+ configure_env.setdefault("CC", "gcc-16")
log(f"Running: {' '.join(configure_args)}")
subprocess.check_call(configure_args, env=configure_env)