diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf9df6..bd84c96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/protocol_reflection_detail/forwarders.hxx b/protocol_reflection_detail/forwarders.hxx new file mode 100644 index 0000000..cfe6111 --- /dev/null +++ b/protocol_reflection_detail/forwarders.hxx @@ -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 +#include + +#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 +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 +using forwarder_base = typename[:make_forwarder_base_info():]; + +// 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 +struct forwarders : Bases... {}; + +consteval std::meta::info forwarders_type( + const std::vector& base_types) { + return std::meta::substitute(^^forwarders, base_types); +} + +} // namespace xyz::reflection_detail + +#endif // XYZ_PROTOCOL_REFLECTION_DETAIL_FORWARDERS_HXX_ diff --git a/protocol_reflection_detail/forwarders_test.cc b/protocol_reflection_detail/forwarders_test.cc new file mode 100644 index 0000000..cc4f4c4 --- /dev/null +++ b/protocol_reflection_detail/forwarders_test.cc @@ -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 + +#include +#include +#include + +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; +using ShoutBase = forwarder_base; +using CountBase = forwarder_base; + +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(static_cast(this)); + const auto* owner = static_cast(base); + return "Hello, " + std::string(visitor) + ", from " + owner->name + "!"; +} + +std::string ShoutWrapper::operator()(std::string_view visitor) const { + const auto* base = + static_cast(static_cast(this)); + const auto* owner = static_cast(base); + return std::string(visitor) + ", " + owner->name + " SHOUTS HELLO!"; +} + +int CountWrapper::operator()() const { + const auto* base = + static_cast(static_cast(this)); + const auto* owner = static_cast(base); + return static_cast(owner->name.size()); +} + +TEST(Forwarders, EachSynthesizedBaseIsARealBaseOfTheOwner) { + static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v); +} + +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; +using OneMemberForwarders = typename[:forwarders_type({^^OneBase}):]; + +struct OneMemberOwner : OneMemberForwarders { + int value = 7; +}; + +int OneWrapper::operator()() const { + const auto* base = + static_cast(static_cast(this)); + const auto* owner = static_cast(base); + return owner->value; +} + +TEST(Forwarders, OneMemberIsABaseAndAddsNoStorage) { + static_assert(std::is_base_of_v); + 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); + static_assert(sizeof(ZeroMemberOwner) == sizeof(int)); +} + +} // namespace