Info
-
Did you know that static reflection can be used to invoke functions with named parameters?
Example
struct foo {
auto api(int i) -> void { std::cout << i; }
};
auto main() -> int {
namespace meta = std::experimental::reflect;
foo f{};
(f.*meta::get_pointer_v<meta::get_element_t<0, meta::get_member_functions_t<reflexpr(foo)>>>)(42); // prints 42
}
Puzzle
- Can you implement invoke which calls given by name function from reflected struct with given named parameters?
template<fixed_string Name> /*TODO*/ invoke{};
struct foo {
auto bar(int value) { bar_called_with = value; }
auto baz(int i, double d) { baz_called_with = {i, d}; }
std::optional<int> bar_called_with{};
std::optional<std::pair<int, double>> baz_called_with{};
};
int main() {
using namespace boost::ut;
"invoke"_test = [] {
should("support single value") = [] {
foo f{};
invoke<"bar">(f, "value"_arg = 42);
expect(not f.baz_called_with and f.bar_called_with and *f.bar_called_with == 42_i);
};
should("support diff order") = [] {
foo f{};
invoke<"baz">(f, "i"_arg = 4, "d"_arg = 2.0);
expect(not f.bar_called_with and f.baz_called_with and *f.baz_called_with == std::pair{4, 2.});
};
should("support diff order") = [] {
foo f{};
invoke<"baz">(f, "d"_arg = 0., "i"_arg = 42);
expect(not f.bar_called_with and f.baz_called_with and *f.baz_called_with == std::pair{42, .0});
};
};
}
Solutions
template<fixed_string Name, class T, class... Args> decltype(auto) invoke(T& t, Args&&...args) {
const auto get_arg = [&] <class P> {
return ([&] -> meta::get_reflected_type_t<meta::get_type_t<P>> {
if constexpr(std::string_view{meta::get_name_v<P>} == Args::name) {
return args.value;
} else {
return {};
}}() + ...); // exploiting +
};
const auto call = [&] <class F> -> bool {
if constexpr (std::string_view{meta::get_name_v<F>} == Name) {
const auto f = meta::get_pointer_v<F>;
using params_t = meta::get_parameters_t<F>;
[&] <class TParams, auto... Ns> (TParams, std::index_sequence<Ns...>) {
(t.*f)(get_arg.template operator()<meta::get_element_t<Ns, TParams>>()...);
}(params_t{}, std::make_index_sequence<meta::get_size_v<params_t>>{});
return true;
} else {
return false;
}
};
using type = meta::get_aliased_t<reflexpr(T)>;
using member_functions_t = meta::get_member_functions_t<type>;
std::apply([&] <class... Fs> (Fs...) {
(call.template operator()<Fs>() or ...);
}, meta::unpack_sequence_t<std::tuple, member_functions_t>{});
}