Info
-
Did you know that static reflection can be used to implement row polymorphism?
Example
struct foo {
int a{};
int b{};
};
struct bar {
int a{};
};
struct missing_a {
int b{};
};
struct row_with_member_a {
constexpr explicit(false) row_with_member_a(const auto& t)
: a{t.a}
{ }
int a{};
};
auto shrink(row_with_member_a r) {
std::cout << r.a;
}
int main() {
//shrink(missing_a{.b = 42}); // error
shrink(foo{.a = 4, .b = 2}); // prints 4
shrink(bar{.a = 42}); // prints 42
}
Puzzle
- Can you implement a generic shrink function which uses row polymorphism technique to shrink input type and returns string output?
template <fixed_string Name, class TValue = void*>
struct row {
static constexpr auto name = Name;
TValue value{};
template <class T>
constexpr auto operator=(const T& t) {
return row<Name, T>{.value = t};
}
};
template <fixed_string Name>
constexpr auto operator""_row() {
return row<Name>{};
}
template <class... Ts>
struct rows; // TODO
auto shrink(rows<row<"a", int>, row<"b", int>> t) {
return std::to_string(t["a"_row]) + ", " + std::to_string(t["b"_row]);
}
struct empty {};
struct nope {
int a{};
};
struct foo {
int a{};
int b{};
};
struct bar {
int c{};
int b{};
int a{};
};
int main() {
using namespace boost::ut;
static_assert(not [](auto t) { return requires { shrink(t); };}(empty{}));
static_assert(not [](auto t) { return requires { shrink(t); }; }(nope{}));
static_assert([](auto t) { return requires { shrink(t); }; }(foo{}));
static_assert([](auto t) { return requires { shrink(t); }; }(bar{}));
using std::literals::string_literals::operator""s;
expect("1, 2"s == shrink(foo{.a = 1, .b = 2}));
expect("2, 4"s == shrink(bar{.c = 8, .b = 4, .a = 2}));
}
Solutions
template <class T, fixed_string Name>
concept has_member_named = []<class... Ts>(type_list<Ts...>) {
return ((std::string_view{meta::get_name_v<Ts>} == Name) or ...);
}(meta::unpack_sequence_t<type_list, meta::get_data_members_t<reflexpr(T)>>{});
template <class T, fixed_string... Names>
concept has_members_named = (has_member_named<T, Names> and ...);
template <class... TRows>
struct rows : TRows... {
template <has_members_named<TRows::name...> T>
rows(const T& t) {
for_each(get_data_members(get_aliased(mirror(T))),
[&](auto mo) {
if constexpr (constexpr std::string_view s = std::data(get_name(mo));
((s == TRows::name) or ...)) {
constexpr fixed_string<std::size(s)> name = std::data(s);
(*this)[row<name>{}] = get_value(mo, t);
}
});
}
template <class N>
constexpr auto operator[](N) -> decltype(auto) {
return [] <class T> (row<N::name, T>& t) -> auto& {
return t.value;
}(*this);
}
};
template <class...>
struct rows;
template <class T, fixed_string Name>
concept shrinkable = [] {
using namespace std::experimental::reflect;
return []<class... Ts>(std::tuple<Ts...>) {
return (... or (std::string_view{get_name_v<Ts>} == Name));
}
(unpack_sequence_t<std::tuple, get_data_members_t<reflexpr(T)>>{});
}();
template <class... Ts>
struct rows : Ts... {
template <class T>
requires(... and shrinkable<T, Ts::name>) constexpr rows(const T &t) {
for_each(get_data_members(mirror(T)), [&](auto member) {
if constexpr (constexpr std::string_view name = get_name(member);
(... or (name == Ts::name))) {
using key = row<fixed_string<size(name)>{data(name)}>;
(*this)[key{}] = get_value(member, t);
}
});
}
template <fixed_string V>
auto &operator[](row<V>) {
return []<class T>(row<V, T> &self) -> T & { return self.value; }(*this);
}
};
template <class TData, class... TRows>
concept HasRows = []<class... TMembers>(std::tuple<TMembers...>) {
return (... and [](std::string_view name) {
return (... or (name == meta::get_name_v<TMembers>));
}(TRows::name));
}
(meta::unpack_sequence_t<std::tuple, meta::get_data_members_t<reflexpr(TData)>>{});
template <class... TRows>
struct rows : TRows... {
template <HasRows<TRows...> TData>
rows(const TData& data) {
for_each(get_data_members(mirror(TData)), [&](auto member) {
constexpr std::string_view name = get_name(member);
(..., [&] {
if constexpr (name == TRows::name) {
static_cast<TRows&>(*this).value = data.*get_pointer(member);
}
}());
});
};
template <fixed_string Name>
constexpr auto& operator[](row<Name>) {
return []<class TValue>(row<Name, TValue> & self) -> auto& { return self.value; }
(*this);
};
};