Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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' || '' }}
33 changes: 33 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <meta>
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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cmake/xyz_add_test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}>")
Expand Down
17 changes: 17 additions & 0 deletions reflection/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
xyz_add_library(
NAME reflection_protocol
ALIAS xyz_protocol::reflection_protocol)
target_sources(
reflection_protocol INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/protocol.h>)

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)
165 changes: 165 additions & 0 deletions reflection/protocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* 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 <cstddef>
#include <memory>
#include <meta>
#include <type_traits>
#include <vector>

namespace xyz::reflection {

template <typename T, typename Allocator>
class protocol;

template <typename T>
class protocol_view;

template <typename T>
struct is_protocol : std::false_type {};

template <typename T, typename Allocator>
struct is_protocol<protocol<T, Allocator>> : std::true_type {};

template <typename T>
struct is_protocol_view : std::false_type {};

template <typename T>
struct is_protocol_view<protocol_view<T>> : std::true_type {};

template <typename T>
concept is_neither_protocol_nor_protocol_view =
!is_protocol<std::remove_cvref_t<T>>::value &&
!is_protocol_view<std::remove_cvref_t<T>>::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<std::meta::info> lhs_params = parameters_of(lhs);
std::vector<std::meta::info> 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, constness, ref-qualifier, and noexcept).
template <typename Interface, typename Concrete>
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 <typename Interface, typename Concrete>
inline constexpr bool conforms_to_v = conforms_to<Interface, Concrete>();

template <typename T, typename Allocator = std::allocator<std::byte>>
class protocol {
public:
// Special member functions.
protocol() = delete;

protocol(const protocol&)
requires std::is_copy_constructible_v<T>;

protocol(protocol&&)
requires std::is_move_constructible_v<T>;

protocol& operator=(const protocol&)
requires std::is_copy_assignable_v<T>;

protocol& operator=(protocol&&)
requires std::is_move_assignable_v<T>;

~protocol(); // Unconstrained.

// Construct from any type U that conforms to the Interface T.
template <typename U>
requires conforms_to_v<T, std::remove_cvref_t<U>> &&
is_neither_protocol_nor_protocol_view<U>
explicit protocol(U&& value);
};

template <typename T>
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 type U that conforms to the Interface T.
template <typename U>
requires conforms_to_v<T, std::remove_cvref_t<U>> &&
is_neither_protocol_nor_protocol_view<U>
explicit protocol_view(const U& object);
};

} // namespace xyz::reflection
#endif // XYZ_REFLECTION_PROTOCOL_H_
Loading
Loading