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
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,20 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT)
protocol_reflection_detail/thunk.hxx
protocol_reflection_detail/thunk_test.cc
protocol_reflection_detail/conformance.hxx
protocol_reflection_detail/conformance_test.cc)
protocol_reflection_detail/conformance_test.cc
protocol_reflection_detail/forwarders.hxx
protocol_reflection_detail/forwarders_test.cc)
target_compile_options(protocol_reflection_detail_test
PRIVATE -freflection)
target_include_directories(protocol_reflection_detail_test
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# GCC 16 trunk cannot stream std::meta::info-typed constants through
# LTO ("internal compiler error: tree code 'meta_type' is not
# supported in LTO streams"), which forwarders.hxx's substitute-built
# base-class lists trigger; this target is a test binary with no need
# for LTO's runtime performance benefit.
set_target_properties(protocol_reflection_detail_test PROPERTIES
INTERPROCEDURAL_OPTIMIZATION OFF)
endif()

add_executable(protocol_benchmark protocol_benchmark.cc)
Expand Down
72 changes: 72 additions & 0 deletions protocol_reflection_detail/forwarders.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* 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.
==============================================================================*/
// Generalizes a one-member forwarder base to a reflection-computed number
// of members. A Wrapper here recovers its owner by casting through the
// one-member base forwarder_base gives it, then a base-to-derived
// static_cast to the owner. This file only assembles the bases; a
// Wrapper's own behavior and its owner's type are defined elsewhere, by
// whatever combines these bases into a concrete forwarding type.
#ifndef XYZ_PROTOCOL_REFLECTION_DETAIL_FORWARDERS_HXX_
#define XYZ_PROTOCOL_REFLECTION_DETAIL_FORWARDERS_HXX_

#include <meta>
#include <vector>

#include "protocol_reflection_detail/naming.hxx"

namespace xyz::reflection_detail {

// A struct equivalent to (for e.g. Wrapper = GreetWrapper, Member =
// ^^Greeter::name):
// struct Incomplete { [[no_unique_address]] GreetWrapper name; };
// Member is a template parameter rather than a function parameter because
// consteval blocks can't use local variables or parameters, only template
// parameters, and define_aggregate must run from one.
template <typename Wrapper, std::meta::info Member>
consteval std::meta::info make_forwarder_base_info() {
struct Incomplete;
consteval {
define_aggregate(^^Incomplete,
{
std::meta::data_member_spec(
^^Wrapper, {
.name = vtable_entry_name(Member),
.no_unique_address = true})});
}
return ^^Incomplete;
}

template <typename Wrapper, std::meta::info Member>
using forwarder_base = typename[:make_forwarder_base_info<Wrapper, Member>():];

// Combines N forwarder_base specializations into one type via ordinary
// multiple inheritance, giving their eventual owner every named forwarder
// at once with no splicing at any call site.
template <typename... Bases>
struct forwarders : Bases... {};

consteval std::meta::info forwarders_type(
const std::vector<std::meta::info>& base_types) {
return std::meta::substitute(^^forwarders, base_types);
}

} // namespace xyz::reflection_detail

#endif // XYZ_PROTOCOL_REFLECTION_DETAIL_FORWARDERS_HXX_
156 changes: 156 additions & 0 deletions protocol_reflection_detail/forwarders_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* 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.
==============================================================================*/
#include "protocol_reflection_detail/forwarders.hxx"

#include <gtest/gtest.h>

#include <string>
#include <string_view>
#include <type_traits>

namespace {

using xyz::reflection_detail::forwarder_base;
using xyz::reflection_detail::forwarders_type;

// Names only: an interface never instantiated, just reflected upon so
// forwarder_base has something to name each synthesized base after.
struct NamingFixture {
std::string greet(std::string_view) const;
std::string shout(std::string_view) const;
int count() const;
};

// --- Three members: GreetBase/ShoutBase/CountBase synthesized via
// forwarder_base instead of hand-written.

struct ThreeMemberOwner;

struct GreetWrapper {
std::string operator()(std::string_view visitor) const;
};

struct ShoutWrapper {
std::string operator()(std::string_view visitor) const;
};

struct CountWrapper {
int operator()() const;
};

using GreetBase = forwarder_base<GreetWrapper, ^^NamingFixture::greet>;
using ShoutBase = forwarder_base<ShoutWrapper, ^^NamingFixture::shout>;
using CountBase = forwarder_base<CountWrapper, ^^NamingFixture::count>;

using ThreeMemberForwarders =
typename[:forwarders_type({^^GreetBase, ^^ShoutBase, ^^CountBase}):];

struct ThreeMemberOwner : ThreeMemberForwarders {
std::string name = "World";
};

std::string GreetWrapper::operator()(std::string_view visitor) const {
const auto* base =
static_cast<const GreetBase*>(static_cast<const void*>(this));
const auto* owner = static_cast<const ThreeMemberOwner*>(base);
return "Hello, " + std::string(visitor) + ", from " + owner->name + "!";
}

std::string ShoutWrapper::operator()(std::string_view visitor) const {
const auto* base =
static_cast<const ShoutBase*>(static_cast<const void*>(this));
const auto* owner = static_cast<const ThreeMemberOwner*>(base);
return std::string(visitor) + ", " + owner->name + " SHOUTS HELLO!";
}

int CountWrapper::operator()() const {
const auto* base =
static_cast<const CountBase*>(static_cast<const void*>(this));
const auto* owner = static_cast<const ThreeMemberOwner*>(base);
return static_cast<int>(owner->name.size());
}

TEST(Forwarders, EachSynthesizedBaseIsARealBaseOfTheOwner) {
static_assert(std::is_base_of_v<GreetBase, ThreeMemberOwner>);
static_assert(std::is_base_of_v<ShoutBase, ThreeMemberOwner>);
static_assert(std::is_base_of_v<CountBase, ThreeMemberOwner>);
}

TEST(Forwarders, InheritedLookupCallsEachNamedForwarderWithNoSplicing) {
ThreeMemberOwner owner;
owner.name = "Owner";

EXPECT_EQ(owner.greet("Reader"), "Hello, Reader, from Owner!");
EXPECT_EQ(owner.shout("Reader"), "Reader, Owner SHOUTS HELLO!");
EXPECT_EQ(owner.count(), 5);
}

TEST(Forwarders, EmptyBasesAddNoStorageBeyondTheOwnersOwnMembers) {
// Three no_unique_address forwarder bases, none adding size: sizeof
// matches a bare std::string member.
EXPECT_EQ(sizeof(ThreeMemberOwner), sizeof(std::string));
}

// --- One member.

struct OneMemberOwner;

struct OneWrapper {
int operator()() const;
};

using OneBase = forwarder_base<OneWrapper, ^^NamingFixture::count>;
using OneMemberForwarders = typename[:forwarders_type({^^OneBase}):];

struct OneMemberOwner : OneMemberForwarders {
int value = 7;
};

int OneWrapper::operator()() const {
const auto* base =
static_cast<const OneBase*>(static_cast<const void*>(this));
const auto* owner = static_cast<const OneMemberOwner*>(base);
return owner->value;
}

TEST(Forwarders, OneMemberIsABaseAndAddsNoStorage) {
static_assert(std::is_base_of_v<OneBase, OneMemberOwner>);
static_assert(sizeof(OneMemberOwner) == sizeof(int));
}

TEST(Forwarders, OneMemberDispatchesCorrectly) {
OneMemberOwner owner;
EXPECT_EQ(owner.count(), 7);
}

// --- Zero members: forwarders<> is a valid, empty combinator.

using ZeroMemberForwarders = typename[:forwarders_type({}):];

struct ZeroMemberOwner : ZeroMemberForwarders {
int value = 0;
};

TEST(Forwarders, ZeroMembersIsAnEmptyTypeAddingNoStorage) {
static_assert(std::is_empty_v<ZeroMemberForwarders>);
static_assert(sizeof(ZeroMemberOwner) == sizeof(int));
}

} // namespace
Loading