diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d32094..f8f6f1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,7 +216,9 @@ if(XYZ_PROTOCOL_IS_NOT_SUBPROJECT) protocol_reflection_detail/members.hxx protocol_reflection_detail/members_test.cc protocol_reflection_detail/types.hxx - protocol_reflection_detail/types_test.cc) + protocol_reflection_detail/types_test.cc + protocol_reflection_detail/vtable_layout.hxx + protocol_reflection_detail/vtable_layout_test.cc) target_compile_options(protocol_reflection_detail_test PRIVATE -freflection) target_include_directories(protocol_reflection_detail_test diff --git a/protocol_reflection_detail/vtable_layout.hxx b/protocol_reflection_detail/vtable_layout.hxx new file mode 100644 index 0000000..4e9427f --- /dev/null +++ b/protocol_reflection_detail/vtable_layout.hxx @@ -0,0 +1,105 @@ +/* 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 shape-only vtable for a bare interface: a struct of named function +// pointers, one per interface member, synthesized with +// std::meta::define_aggregate. +// +// define_aggregate can only be called on an incomplete type declared at the +// exact same scope as the consteval block that completes it: neither +// completing a class from inside its own body, nor reaching into a sibling +// class template's body from an enclosing function or class, is accepted +// (the compiler rejects both as "intervenes between 'consteval' block ... +// and ... scope"). Each vtable type here is therefore a local struct +// declared inside its own consteval function template, completed in that +// same function, and exposed as a type alias via a splice: one instantiation +// per Interface, giving each Interface its own type. +#ifndef XYZ_PROTOCOL_REFLECTION_DETAIL_VTABLE_LAYOUT_HXX_ +#define XYZ_PROTOCOL_REFLECTION_DETAIL_VTABLE_LAYOUT_HXX_ + +#include +#include + +#include "protocol_reflection_detail/members.hxx" +#include "protocol_reflection_detail/naming.hxx" +#include "protocol_reflection_detail/types.hxx" + +namespace xyz::reflection_detail { + +// One data_member_spec per dispatchable member of `interface` (const-only +// if `const_only`), each a named function pointer of the member's vtable +// entry type, erased through `erased_pointer_type`. +consteval std::vector define_vtable_entries( + std::meta::info interface, std::meta::info erased_pointer_type, + bool const_only) { + std::vector specs; + for (std::meta::info member : interface_member_functions(interface)) { + if (const_only && !std::meta::is_const(member)) { + continue; + } + specs.push_back(std::meta::data_member_spec( + vtable_entry_pointer_type(member, erased_pointer_type), + {.name = vtable_entry_name(member)})); + } + return specs; +} + +template +consteval std::meta::info make_const_view_vtable_info() { + struct Incomplete; + consteval { + define_aggregate(^^Incomplete, + define_vtable_entries(^^Interface, ^^const void*, + /*const_only=*/true)); + } + return ^^Incomplete; +} + +// The const-only vtable for Interface: one named function pointer per +// const member, each taking a `const void*` in place of the implicit +// object parameter. +template +using const_view_vtable = typename[:make_const_view_vtable_info():]; + +template +consteval std::meta::info make_view_vtable_info() { + struct Incomplete; + consteval { + std::vector specs = {std::meta::data_member_spec( + ^^const_view_vtable, { + .name = "const_view"})}; + for (std::meta::info spec : + define_vtable_entries(^^Interface, ^^void*, /*const_only=*/false)) { + specs.push_back(spec); + } + define_aggregate(^^Incomplete, specs); + } + return ^^Incomplete; +} + +// The full vtable for Interface: a `const_view` sub-object plus one named +// function pointer per member, including the const ones again, each taking +// a plain `void*` since a mutable view's entries all need mutable access +// even when the member itself is const. +template +using view_vtable = typename[:make_view_vtable_info():]; + +} // namespace xyz::reflection_detail + +#endif // XYZ_PROTOCOL_REFLECTION_DETAIL_VTABLE_LAYOUT_HXX_ diff --git a/protocol_reflection_detail/vtable_layout_test.cc b/protocol_reflection_detail/vtable_layout_test.cc new file mode 100644 index 0000000..e19d3a0 --- /dev/null +++ b/protocol_reflection_detail/vtable_layout_test.cc @@ -0,0 +1,70 @@ +/* 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/vtable_layout.hxx" + +#include + +#include +#include + +namespace { + +using xyz::reflection_detail::const_view_vtable; +using xyz::reflection_detail::view_vtable; + +struct Fixture { + int value() const { return 0; } + + void set_value(int) {} +}; + +TEST(ConstViewVtable, HasOneEntryPerConstMemberTakingAConstErasedPointer) { + static_assert( + std::is_same_v>().value), + int (*)(const void*)>); + static_assert(sizeof(const_view_vtable) == sizeof(void (*)())); +} + +TEST(ViewVtable, HasAConstViewSubobjectAndOneEntryPerMember) { + static_assert( + std::is_same_v>().const_view), + const_view_vtable>); + static_assert( + std::is_same_v>().value), + int (*)(void*)>); + // set_value's own literal underscore is escaped too (naming.hxx escapes + // every non-alphanumeric byte, including "_" itself): set_5fvalue. + static_assert( + std::is_same_v>().set_5fvalue), + void (*)(void*, int)>); +} + +TEST(ViewVtable, EntriesAreCallableFunctionPointers) { + view_vtable vtable{ + .const_view = {.value = +[](const void*) { return 42; }}, + .value = +[](void*) { return 42; }, + .set_5fvalue = +[](void*, int) {}}; + + EXPECT_EQ(vtable.const_view.value(nullptr), 42); + EXPECT_EQ(vtable.value(nullptr), 42); + vtable.set_5fvalue(nullptr, 1); +} + +} // namespace