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
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions protocol_reflection_detail/vtable_layout.hxx
Original file line number Diff line number Diff line change
@@ -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 <meta>
#include <vector>

#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<std::meta::info> define_vtable_entries(
std::meta::info interface, std::meta::info erased_pointer_type,
bool const_only) {
std::vector<std::meta::info> 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 <typename Interface>
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 <typename Interface>
using const_view_vtable = typename[:make_const_view_vtable_info<Interface>():];

template <typename Interface>
consteval std::meta::info make_view_vtable_info() {
struct Incomplete;
consteval {
std::vector<std::meta::info> specs = {std::meta::data_member_spec(
^^const_view_vtable<Interface>, {
.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 <typename Interface>
using view_vtable = typename[:make_view_vtable_info<Interface>():];

} // namespace xyz::reflection_detail

#endif // XYZ_PROTOCOL_REFLECTION_DETAIL_VTABLE_LAYOUT_HXX_
70 changes: 70 additions & 0 deletions protocol_reflection_detail/vtable_layout_test.cc
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

#include <type_traits>
#include <utility>

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<decltype(std::declval<const_view_vtable<Fixture>>().value),
int (*)(const void*)>);
static_assert(sizeof(const_view_vtable<Fixture>) == sizeof(void (*)()));
}

TEST(ViewVtable, HasAConstViewSubobjectAndOneEntryPerMember) {
static_assert(
std::is_same_v<decltype(std::declval<view_vtable<Fixture>>().const_view),
const_view_vtable<Fixture>>);
static_assert(
std::is_same_v<decltype(std::declval<view_vtable<Fixture>>().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<decltype(std::declval<view_vtable<Fixture>>().set_5fvalue),
void (*)(void*, int)>);
}

TEST(ViewVtable, EntriesAreCallableFunctionPointers) {
view_vtable<Fixture> 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
Loading