Named, static dispatching with std::variant
Another solution:
using State = Visitor::State;
template<class Visitor>
struct VisitorProxy {
State s;
template<class E>
auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
return Visitor::apply(s, e);
}
template<class E>
State operator()(E const&) const {
return s;
}
};
template <typename Visitor> struct Applicator {
static State apply(State s, Event e) {
VisitorProxy<Visitor> p{s};
return std::visit(p, e);
}
};
Using the now quite common overloaded
class template trick (And Maxim's trick to order the lambdas based on the const
ness of their operator()
) to create a SFINAE-capable functor modeling the logic you're lookig for:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
// ...
template <typename Visitor> struct Applicator {
static typename Visitor::State apply(typename Visitor::State s, Event e) {
return std::visit(overloaded{
[&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
[&s](auto) { return s; }
}, e);
}
};
Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)
Well, std::is_invocable_r
looks like the tool of choice.
Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.
Instead, go one step back and use std::is_detected
from library fundamentals TS v2 or equivalent and a template:
template <class... Xs>
using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));
if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
return Visitor::apply(s, e);
The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.