I need to fill in some template magic to make the following code snippet to work.
The problem is that I want to be able to define a visitor class for std::variant
with named static methods accepting two arguments. How can I fill in Applicator::apply()
to make the dispatching work?
struct EventA {};
struct EventB {};
struct EventC {};
using Event = std::variant<EventA, EventB, EventC>;
struct Visitor {
enum class LastEvent { None, A, B, C };
struct State {
LastEvent last_event = LastEvent::None;
};
static State apply(State s, EventA e) { return State{LastEvent::A}; }
static State apply(State s, EventB e) { return State{LastEvent::B}; }
};
template <typename Visitor> struct Applicator {
static State apply(State s, Event e) {
/*** Start of pseudo code ***/
if (Visitor can apply) {
return Visitor::apply(s, e);
}
/*** End of pseudo code ***/
// Else, don't update state state
return s;
}
};
int main() {
// Handled by visitor
State s1 = Applicator<Visitor>::apply(State{}, EventA{});
assert(s1.last_event == Visitor::LastEvent::A);
// Handled by visitor
State s2 = Applicator<Visitor>::apply(State{}, EventB{});
assert(s2.last_event == Visitor::LastEvent::B);
// NOT handled by visitor
State s3 = Applicator<Visitor>::apply(State{}, EventC{});
assert(s3.last_event == Visitor::LastEvent::None);
}
One additional thing is that std::variant does not dynamically allocate memory for the values. Any instance of std::variant at any given time either holds a value of one of its alternative types, or it holds no value at all.
Empty variants are also ill-formed (std::variant<std::monostate> can be used instead). A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type.
std::monostate is a value-semantic type, like bool — it just has one fewer value in its domain. It can be stored in variants, or containers, or set s (it's ordered!), or unordered_set s (it's hashable!), or anywhere else you might use a value-semantic type like bool .
Boost. Variant, part of collection of the Boost C++ Libraries. It is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner.
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With