decltype(fields)::Type> && ...)
: MetaAggregate(other.refs())
{
}
MetaAggregate& operator=(const MetaAggregate& other)
requires(std::copyable<typename decltype(fields)::Type> && ...)
{
std::apply(
[&](const auto&... from) {
std::apply(
[&]<typename... To>(To&&... to) { ((std::forward<To>(to) = from), ...); },
refs()
);
},
other.refs()
);
return *this;
}
MetaAggregate& operator=(MetaAggregate&& other)
requires(std::movable<typename decltype(fields)::Type> && ...)
{
std::apply(
[&](auto&&... from) {
std::apply(
[&](auto&&... to) {
((std::forward<decltype(to)>(to) = std::move(from)), ...);
},
refs()
);
},
other.refs()
);
return *this;
}
template <typename... Init>
MetaAggregate(Init&&... init)
requires(std::constructible_from<typename decltype(fields)::Type, Init> && ...)
: MetaAggregate(std::forward_as_tuple(std::forward<Init>(init)...))
{
}
template <typename... Init>
MetaAggregate(std::tuple<Init...> init_tuple)
requires(std::constructible_from<typename decltype(fields)::Type, Init> && ...)
{
std::apply(
[&](Init&&... init) {
std::apply(
[&](auto... offset) {
(new (storage.data() + offset) decltype(fields)::Type(
std::forward<Init>(init)
),
...);
},
info.offsets
);
},
std::move(init_tuple)
);
}
~MetaAggregate()
requires(std::destructible<typename decltype(fields)::Type> && ...)
{
std::apply([]<typename... T>(T&... objects) { (objects.~T(), ...); }, refs());
}
A little function to let us check that we got the layout right:
static void dump_layout(std::string_view struct_name = "MetaAggregate<...>")
{
std::array type_names = { typeid(typename decltype(fields)::Type).name()... };
std::println("Size of {}: {}", struct_name, info.total_size);
std::println("Alignment of {}: {}", struct_name, info.max_align);
std::println("Fields:");
for (auto [type_name, name, offset, size, align] :
std::views::zip(type_names, info.names, info.offsets, info.sizes, info.aligns))
{
std::println(
" - {} {} (offset: {}; size: {}; alignment: {})", type_name, name, offset, size,
align
);
}
}
Now we get to finally access the fields. First by index:
template <
std::size_t index, typename Self,
typename Type = std::tuple_element_t<index, decltype(std::tuple{ fields... })>::Type>
decltype(auto) get(this Self&& self)
{
using Ptr =
std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const Type, Type>*;
constexpr auto offset = std::get<index>(info.offsets);
return std::forward_like<Self>(*reinterpret_cast<Ptr>(self.storage.data() + offset));
}
and finally by name. Note that we convert the name into an index at **compile time** (that's why we do all that stuff with UDLs).
template <std::size_t size, std::array<char, size> name>
static consteval std::size_t index(detail::FieldByName<name>)
{
constexpr std::array matches{ std::string_view(name) == std::string_view(fields.name)... };
constexpr auto num_matches = std::ranges::count_if(matches, std::identity{});
static_assert(num_matches > 0, "field not found");
static_assert(num_matches < 2, "multiple fields match name");
return std::distance(matches.begin(), std::ranges::find_if(matches, std::identity{}));
}
template <std::size_t size, std::array<char, size> name>
decltype(auto) operator[](this auto&& self, detail::FieldByName<name>)
{
return self.template get<index<size, name>({})>();
}
This method lets us avoid having `index_sequence`s everywhere (and also gives us some structured binding support):
template <typename Self>
auto refs(this Self&& self)
{
return [&]<std::size_t... index>(std::index_sequence<index...>) {
return std::forward_as_tuple(std::forward<Self>(self).template get<index>()...);
}(std::make_index_sequence<sizeof...(fields)>{});
}
And last but not least, our storage:
private:
alignas(info.max_align)
Post #25151
6