diff --git a/CMakeLists.txt b/CMakeLists.txt index f8f6f1f..a7de62e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,7 +218,9 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT) protocol_reflection_detail/types.hxx protocol_reflection_detail/types_test.cc protocol_reflection_detail/vtable_layout.hxx - protocol_reflection_detail/vtable_layout_test.cc) + protocol_reflection_detail/vtable_layout_test.cc + protocol_reflection_detail/thunk.hxx + protocol_reflection_detail/thunk_test.cc) target_compile_options(protocol_reflection_detail_test PRIVATE -freflection) target_include_directories(protocol_reflection_detail_test diff --git a/protocol_reflection_detail/thunk.hxx b/protocol_reflection_detail/thunk.hxx new file mode 100644 index 0000000..f3057d8 --- /dev/null +++ b/protocol_reflection_detail/thunk.hxx @@ -0,0 +1,55 @@ +/* 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. +==============================================================================*/ +// The static function stored in a vtable entry: casts the erased pointer +// back to Implementation and forwards the call through MergedCandidates, a +// default-constructible callable invoked as +// MergedCandidates{}(implementation_reference, args...). This thunk only +// requires MergedCandidates to be callable that way, so it can be tested +// here against a hand-rolled fake candidate. +#ifndef XYZ_PROTOCOL_REFLECTION_DETAIL_THUNK_HXX_ +#define XYZ_PROTOCOL_REFLECTION_DETAIL_THUNK_HXX_ + +#include +#include + +namespace xyz::reflection_detail { + +template +struct erased_call_thunk; + +template +struct erased_call_thunk { + using erased_pointer_type = + std::conditional_t; + using implementation_pointer_type = + std::conditional_t; + + static R call(erased_pointer_type erased, Args... args) noexcept(IsNoexcept) { + auto* implementation = static_cast(erased); + return MergedCandidates{}(*implementation, std::forward(args)...); + } +}; + +} // namespace xyz::reflection_detail + +#endif // XYZ_PROTOCOL_REFLECTION_DETAIL_THUNK_HXX_ diff --git a/protocol_reflection_detail/thunk_test.cc b/protocol_reflection_detail/thunk_test.cc new file mode 100644 index 0000000..03d1371 --- /dev/null +++ b/protocol_reflection_detail/thunk_test.cc @@ -0,0 +1,71 @@ +/* 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/thunk.hxx" + +#include + +namespace { + +using xyz::reflection_detail::erased_call_thunk; + +struct Implementation { + int value = 0; + + int get() const { return value; } + + void set(int v) { value = v; } +}; + +// Hand-rolled fake candidates: each is just a default-constructible callable +// taking the implementation reference plus the call's own arguments. +struct GetCandidate { + int operator()(const Implementation& impl) const { return impl.get(); } +}; + +struct SetCandidate { + void operator()(Implementation& impl, int v) const { impl.set(v); } +}; + +TEST(ErasedCallThunk, ConstErasedCallRoundTripsTheReturnValue) { + Implementation impl{.value = 42}; + using Thunk = + erased_call_thunk; + EXPECT_EQ(Thunk::call(&impl), 42); +} + +TEST(ErasedCallThunk, NonConstErasedCallForwardsArgumentsAndMutates) { + Implementation impl; + using Thunk = + erased_call_thunk; + Thunk::call(&impl, 7); + EXPECT_EQ(impl.value, 7); +} + +TEST(ErasedCallThunk, + IsNoexceptTemplateParameterControlsTheThunksNoexceptness) { + using NoexceptThunk = + erased_call_thunk; + using ThrowingThunk = + erased_call_thunk; + static_assert(noexcept(NoexceptThunk::call(nullptr))); + static_assert(!noexcept(ThrowingThunk::call(nullptr))); +} + +} // namespace