From 567592f19128cb2358d55d648619938ff0c8b7ce Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 07:24:02 +0000
Subject: [PATCH 1/9] Initial plan
From 9aefd5ecb17664a0b4a45067e6b1131c99f43f35 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 07:38:20 +0000
Subject: [PATCH 2/9] Add C++26 reflection conformance checks for protocol and
protocol_view
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 | 124 +++++++++++++++++++
reflection/protocol_test.cc | 234 ++++++++++++++++++++++++++++++++++++
5 files changed, 409 insertions(+), 1 deletion(-)
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..07a2e33 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()
@@ -98,6 +127,10 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
enable_testing()
+ if(XYZ_PROTOCOL_BUILD_REFLECTION_IMPLEMENTATION)
+ add_subdirectory(reflection)
+ endif()
+
xyz_generate_protocol(
CLASS_NAME A INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/interface_A.h
HEADER interface_A.h
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..ebb9967
--- /dev/null
+++ b/reflection/protocol.h
@@ -0,0 +1,124 @@
+/* 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.
+
+#include
+#include
+#include
+#include
+#include
+
+namespace xyz::reflection {
+
+namespace detail {
+
+// Returns true if both member functions share the same name, return type,
+// parameter types, and constness.
+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 (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;
+}
+
+} // 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, and constness).
+template
+consteval bool conforms_to() {
+ for (std::meta::info interface_member :
+ members_of(^^Interface, std::meta::access_context::unprivileged())) {
+ if (!is_member_function(interface_member)) continue;
+ bool found = false;
+ for (std::meta::info concrete_member :
+ members_of(^^Concrete, std::meta::access_context::unprivileged())) {
+ if (!is_member_function(concrete_member)) continue;
+ if (detail::member_function_signatures_match(interface_member,
+ concrete_member)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) return false;
+ }
+ return true;
+}
+
+template >
+class protocol {
+ 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 Concrete type that conforms to the Interface T.
+ template
+ requires conforms_to>()
+ explicit protocol(Concrete&& value);
+};
+
+template
+class protocol_view {
+ 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 Concrete type that conforms to the Interface T.
+ template
+ requires conforms_to>()
+ explicit protocol_view(const Concrete& 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..d2f9335
--- /dev/null
+++ b/reflection/protocol_test.cc
@@ -0,0 +1,234 @@
+// Tests for the C++26-reflection-based implementation of protocol and
+// protocol_view.
+
+#include "protocol.h"
+
+#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>);
+}
+
+TEST(ReflectionProtocolViewTest,
+ CheckSpecialMembersForStructWithDeletedSpecialMembers) {
+ // protocol_view's special member functions do not depend on those of the
+ // viewed type.
+ struct D {
+ D() = delete;
+ D(const D&) = delete;
+ D(D&&) = delete;
+ D& operator=(const D&) = delete;
+ D& operator=(D&&) = delete;
+ ~D() = delete;
+ };
+
+ 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>);
+}
+
+TEST(ReflectionProtocolTest,
+ CheckSpecialMembersForStructWithDeletedSpecialMembers) {
+ // protocol is not default-constructible and cannot be copied, moved,
+ // assigned, or move assigned if the underlying type cannot be.
+ struct D {
+ D() = delete;
+ D(const D&) = delete;
+ D(D&&) = delete;
+ D& operator=(const D&) = delete;
+ D& operator=(D&&) = delete;
+ };
+
+ 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>);
+}
+
+// ---------------------------------------------------------------------------
+// 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());
+}
+
+} // namespace
From e491e1525202913644cbca27cdcbbef4f9700fc1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 10:37:11 +0000
Subject: [PATCH 3/9] Fix clang-format: use conforms_to_v variable template in
requires clauses
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 8 ++++++--
reflection/protocol_test.cc | 18 ++++++++++++++++++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index ebb9967..70f1063 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -77,6 +77,10 @@ consteval bool conforms_to() {
return true;
}
+// Variable template for use in requires clauses.
+template
+inline constexpr bool conforms_to_v = conforms_to();
+
template >
class protocol {
public:
@@ -99,7 +103,7 @@ class protocol {
// Construct from any Concrete type that conforms to the Interface T.
template
- requires conforms_to>()
+ requires conforms_to_v>
explicit protocol(Concrete&& value);
};
@@ -116,7 +120,7 @@ class protocol_view {
// Construct from any Concrete type that conforms to the Interface T.
template
- requires conforms_to>()
+ requires conforms_to_v>
explicit protocol_view(const Concrete& object);
};
diff --git a/reflection/protocol_test.cc b/reflection/protocol_test.cc
index d2f9335..600af3e 100644
--- a/reflection/protocol_test.cc
+++ b/reflection/protocol_test.cc
@@ -94,6 +94,7 @@ TEST(ReflectionProtocolTest,
TEST(ConformsToTest, EmptyInterfaceIsAlwaysSatisfied) {
struct EmptyInterface {};
+
struct Concrete {};
static_assert(conforms_to());
@@ -104,8 +105,10 @@ TEST(ConformsToTest, ConcreteTypeConformsWhenAllMethodsMatch) {
std::string_view name() const noexcept;
int count();
};
+
struct Concrete {
std::string_view name() const noexcept { return "test"; }
+
int count() { return 42; }
};
@@ -116,9 +119,12 @@ TEST(ConformsToTest, ConcreteTypeConformsWithExtraMethodsPresent) {
struct Interface {
void process();
};
+
struct ConcreteWithExtra {
void process() {}
+
void extra_method() {}
+
int another() const { return 0; }
};
@@ -130,6 +136,7 @@ TEST(ConformsToTest, ConcreteTypeMissingMethodDoesNotConform) {
void foo();
void bar();
};
+
struct MissingBar {
void foo() {}
};
@@ -141,6 +148,7 @@ TEST(ConformsToTest, WrongConstnessDoesNotConform) {
struct Interface {
int value() const;
};
+
struct NonConst {
int value(); // not const — does not match the interface
};
@@ -152,6 +160,7 @@ TEST(ConformsToTest, ConstMethodNotSatisfiedByNonConstDoesNotConform) {
struct Interface {
void process() const;
};
+
struct NonConstProcess {
void process() {} // missing const qualifier
};
@@ -163,6 +172,7 @@ TEST(ConformsToTest, WrongReturnTypeDoesNotConform) {
struct Interface {
int compute();
};
+
struct WrongReturn {
double compute() { return 0.0; }
};
@@ -174,6 +184,7 @@ TEST(ConformsToTest, WrongParameterTypeDoesNotConform) {
struct Interface {
void process(int value);
};
+
struct WrongParam {
void process(double value) {}
};
@@ -185,6 +196,7 @@ TEST(ConformsToTest, WrongParameterCountDoesNotConform) {
struct Interface {
void process(int a, int b);
};
+
struct WrongArity {
void process(int a) {}
};
@@ -196,6 +208,7 @@ TEST(ConformsToTest, MultipleParametersMatchCorrectly) {
struct Interface {
void write(int length, double value);
};
+
struct Concrete {
void write(int length, double value) {}
};
@@ -208,8 +221,10 @@ TEST(ConformsToTest, ConcreteTypeConformsForTypicalInterfaceA) {
std::string_view name() const noexcept;
int count();
};
+
struct ConcreteA {
std::string_view name() const noexcept { return "concrete"; }
+
int count() { return 1; }
};
@@ -222,9 +237,12 @@ TEST(ConformsToTest, ConcreteTypeConformsForTypicalInterfaceB) {
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; }
};
From 0bfac4e1360fb3d2bb35de7b9f923ffa1b606214 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 10:44:58 +0000
Subject: [PATCH 4/9] Use U instead of Concrete in constructor templates; add
constructability tests
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 16 ++++++++--------
reflection/protocol_test.cc | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index 70f1063..a65fa30 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -101,10 +101,10 @@ class protocol {
~protocol(); // Unconstrained.
- // Construct from any Concrete type that conforms to the Interface T.
- template
- requires conforms_to_v>
- explicit protocol(Concrete&& value);
+ // Construct from any type U that conforms to the Interface T.
+ template
+ requires conforms_to_v>
+ explicit protocol(U&& value);
};
template
@@ -118,10 +118,10 @@ class protocol_view {
protocol_view& operator=(protocol_view&&) = default;
~protocol_view() = default;
- // Construct from any Concrete type that conforms to the Interface T.
- template
- requires conforms_to_v>
- explicit protocol_view(const Concrete& object);
+ // Construct from any type U that conforms to the Interface T.
+ template
+ requires conforms_to_v>
+ explicit protocol_view(const U& object);
};
} // namespace xyz::reflection
diff --git a/reflection/protocol_test.cc b/reflection/protocol_test.cc
index 600af3e..66134ff 100644
--- a/reflection/protocol_test.cc
+++ b/reflection/protocol_test.cc
@@ -88,6 +88,41 @@ TEST(ReflectionProtocolTest,
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.
// ---------------------------------------------------------------------------
From e118a913e4da48a48c069be74b4bccec52de5d75 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 10:51:54 +0000
Subject: [PATCH 5/9] Add C++26 reflection CI: --reflection flag in cmake.py
and GCC-16 matrix in cmake.yml
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
.github/workflows/cmake.yml | 20 +++++++++++++++++++-
scripts/cmake.py | 20 +++++++++++++++++++-
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
index bcad3fd..e66776d 100644
--- a/.github/workflows/cmake.yml
+++ b/.github/workflows/cmake.yml
@@ -211,6 +211,18 @@ jobs:
std: 20,
},
}
+ - {
+ name: "Ubuntu GCC-16 (C++26 Reflection)",
+ os: ubuntu-24.04,
+ compiler:
+ {
+ type: GCC16,
+ version: 16,
+ cc: "gcc-16",
+ cxx: "g++-16",
+ std: 26,
+ },
+ }
steps:
- uses: actions/checkout@v4
- uses: seanmiddleditch/gha-setup-ninja@master
@@ -231,9 +243,15 @@ jobs:
with:
version: ${{ matrix.settings.compiler.version }}
platform: x64
+ - name: Install GCC 16 from ubuntu-toolchain-r PPA
+ if: matrix.settings.compiler.type == 'GCC16'
+ run: |
+ sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends g++-16 gcc-16
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv sync
- name: Run CMake
- run: ./scripts/cmake.sh --${{ matrix.configuration == 'Debug' && 'debug' || 'release' }}
+ run: ./scripts/cmake.sh --${{ matrix.configuration == 'Debug' && 'debug' || 'release' }} ${{ matrix.settings.compiler.type == 'GCC16' && '--reflection' || '' }}
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 b88fa43f781d4197226d18759b93895b9d2b350b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 11:31:07 +0000
Subject: [PATCH 6/9] Check noexcept and ref-qualifier in conforms_to; add
tests
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 16 +++++--
reflection/protocol_test.cc | 90 +++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+), 3 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index a65fa30..2e1bd26 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -33,13 +33,23 @@ namespace xyz::reflection {
namespace detail {
-// Returns true if both member functions share the same name, return type,
-// parameter types, and constness.
+// 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);
@@ -56,7 +66,7 @@ consteval bool member_function_signatures_match(std::meta::info lhs,
// 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, and constness).
+// type, parameter types, constness, ref-qualifier, and noexcept).
template
consteval bool conforms_to() {
for (std::meta::info interface_member :
diff --git a/reflection/protocol_test.cc b/reflection/protocol_test.cc
index 66134ff..d373d90 100644
--- a/reflection/protocol_test.cc
+++ b/reflection/protocol_test.cc
@@ -284,4 +284,94 @@ TEST(ConformsToTest, ConcreteTypeConformsForTypicalInterfaceB) {
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());
+}
+
} // namespace
From e95c191e4789d01e577075d77f9b0f4477dfd42b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 11:45:05 +0000
Subject: [PATCH 7/9] Fix CI: replace is_member_function with is_function (not
in GCC-16 P2996)
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index 2e1bd26..96dedae 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -71,11 +71,11 @@ template
consteval bool conforms_to() {
for (std::meta::info interface_member :
members_of(^^Interface, std::meta::access_context::unprivileged())) {
- if (!is_member_function(interface_member)) continue;
+ if (!is_function(interface_member)) continue;
bool found = false;
for (std::meta::info concrete_member :
members_of(^^Concrete, std::meta::access_context::unprivileged())) {
- if (!is_member_function(concrete_member)) continue;
+ if (!is_function(concrete_member)) continue;
if (detail::member_function_signatures_match(interface_member,
concrete_member)) {
found = true;
From f104592fbb5f1dac9c00835dac463da897802302 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 12:57:01 +0000
Subject: [PATCH 8/9] Fix GCC16 reflection conformance and constructor
constraints
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol.h | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/reflection/protocol.h b/reflection/protocol.h
index 96dedae..e05214c 100644
--- a/reflection/protocol.h
+++ b/reflection/protocol.h
@@ -31,6 +31,29 @@ SOFTWARE.
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
@@ -72,10 +95,12 @@ 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;
@@ -113,7 +138,8 @@ class protocol {
// Construct from any type U that conforms to the Interface T.
template
- requires conforms_to_v>
+ requires conforms_to_v> &&
+ is_neither_protocol_nor_protocol_view
explicit protocol(U&& value);
};
@@ -130,7 +156,8 @@ class protocol_view {
// Construct from any type U that conforms to the Interface T.
template
- requires conforms_to_v>
+ requires conforms_to_v> &&
+ is_neither_protocol_nor_protocol_view
explicit protocol_view(const U& object);
};
From 70f705db325d30fb666d334da48006030c555050 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 13:04:05 +0000
Subject: [PATCH 9/9] Remove tests for functions without identifiers (out of
scope)
Co-authored-by: jbcoe <777363+jbcoe@users.noreply.github.com>
---
reflection/protocol_test.cc | 40 -------------------------------------
1 file changed, 40 deletions(-)
diff --git a/reflection/protocol_test.cc b/reflection/protocol_test.cc
index d373d90..a20b70d 100644
--- a/reflection/protocol_test.cc
+++ b/reflection/protocol_test.cc
@@ -32,27 +32,6 @@ TEST(ReflectionProtocolViewTest, CheckSpecialMembers) {
static_assert(std::is_destructible_v>);
}
-TEST(ReflectionProtocolViewTest,
- CheckSpecialMembersForStructWithDeletedSpecialMembers) {
- // protocol_view's special member functions do not depend on those of the
- // viewed type.
- struct D {
- D() = delete;
- D(const D&) = delete;
- D(D&&) = delete;
- D& operator=(const D&) = delete;
- D& operator=(D&&) = delete;
- ~D() = delete;
- };
-
- 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).
// ---------------------------------------------------------------------------
@@ -69,25 +48,6 @@ TEST(ReflectionProtocolTest, CheckSpecialMembers) {
static_assert(std::is_move_assignable_v>);
}
-TEST(ReflectionProtocolTest,
- CheckSpecialMembersForStructWithDeletedSpecialMembers) {
- // protocol is not default-constructible and cannot be copied, moved,
- // assigned, or move assigned if the underlying type cannot be.
- struct D {
- D() = delete;
- D(const D&) = delete;
- D(D&&) = delete;
- D& operator=(const D&) = delete;
- D& operator=(D&&) = delete;
- };
-
- 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.
// ---------------------------------------------------------------------------