Info
-
Did you know C++2X Pattern Matching can be used for run-time dispatching?
Example
template<auto...> struct ids{};
template<auto N, auto... Ns>
auto dispatch(auto value, ids<N, Ns...>) -> decltype(value) {
return inspect (value) {
N => value;
_ => [] {
if constexpr (sizeof...(Ns) > 0) {
return dispatch(value, ids<Ns...>{});
} else {
return {};
}
}()
};
}
int main() {
std::cout << dispatch(0, ids<1, 2, 3>{}); // prints 0
std::cout << dispatch(4, ids<1, 2, 3>{}); // prints 0
std::cout << dispatch(1, ids<1, 2, 3>{}); // prints 1
std::cout << dispatch(2, ids<1, 2, 3>{}); // prints 2
std::cout << dispatch(3, ids<1, 2, 3>{}); // prints 3
}
Puzzle
-
Can you implement different run-time dispatching policies and apply them to process events?
-
Policies ideas
- if/else
- switch
- inspect
- fold expressions
- jump table
- goto table
- ...
-
Double points for the most innovative policy
-
template <class TPolicy, class TMsg, class... Ts>
concept dispatch_policy = requires(TPolicy policy, const TMsg& msg,
std::size_t& state) {
policy.template operator()<Ts...>(msg, state);
};
constexpr auto example_policy = []<class... Ts>(const auto& msg, auto& state) {
/*TODO*/
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
/*TODO*/
return state_;
}
constexpr explicit(false) sm(const Transitions&...) { }
private:
std::size_t state_{};
};
template <class TSrc, class TMsg, class TDst>
struct transition {
using src = TSrc;
using msg = TMsg;
using dst = TDst;
};
struct msg0 {};
struct msg1 {};
struct msg2 {};
struct msg3 {};
struct msg4 {};
int main() {
using namespace boost::ut;
"state machine"_test =
[](const auto& dispatch_policy) {
sm sm{transition<class State1, msg1, class State2>{},
transition<class State2, msg2, class State1>{}};
should("state in the same state on unexpected message") = [&] {
expect(0_i == sm.process(msg0{}, dispatch_policy));
expect(0_i == sm.process(msg2{}, dispatch_policy));
};
should("transition to destination state on expected message") = [&] {
expect(1_i == sm.process(msg1{}, dispatch_policy));
};
should("stay in the same state on unexpected message") = [&] {
expect(1_i == sm.process(msg0{}, dispatch_policy));
expect(1_i == sm.process(msg1{}, dispatch_policy));
};
should("transition to source state on expected message") = [&] {
expect(0_i == sm.process(msg2{}, dispatch_policy));
};
}
// policies
| std::tuple{example_policy};
}
Solutions
constexpr auto example_policy = []<class... Ts>(const auto& msg, auto& state) {
using states_t = boost::mp11::mp_unique<
boost::mp11::mp_list<typename Ts::src..., typename Ts::dst...>>;
(
[&state] {
if constexpr (std::is_same_v<std::decay_t<decltype(msg)>,
typename Ts::msg>) {
if (state == boost::mp11::mp_find<states_t, typename Ts::src>{}) {
state = boost::mp11::mp_find<states_t, typename Ts::dst>{};
}
}
}(),
...);
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
policy.template operator()<Transitions...>(msg, state_);
return state_;
}
constexpr explicit(false) sm(const Transitions&...) {}
private:
int state_{};
};
constexpr auto example_policy = []<class... Ts>(const auto& msg, auto& state){
using states_t = boost::mp11::mp_unique<boost::mp11::mp_list<typename Ts::src..., typename Ts::dst...>>;
([&state] {
if constexpr (std::is_same_v<std::decay_t<decltype(msg)>, typename Ts::msg>) {
if (state == boost::mp11::mp_find<states_t, typename Ts::src>{}) {
state = boost::mp11::mp_find<states_t, typename Ts::dst>{};
}
}
}(), ...);
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
policy.template operator()<Transitions...>(msg, state_);
return state_;
}
constexpr explicit(false) sm(const Transitions&...) {}
private:
std::size_t state_{};
};
constexpr auto only_thursdays_policy = []<class... TTransitions>(const auto& msg, auto& state){
using msg_t = std::decay_t<decltype(msg)>;
([&] <typename TTransition> () {
if constexpr (std::is_same_v<msg_t, typename TTransition::msg>) {
if (boost::mp11::mp_find<boost::mp11::mp_list<TTransitions...>, TTransition>{} == state) {
using namespace std::chrono;
const auto d = floor<days>(system_clock::now());
if (const year_month_weekday ymd{d}; ymd.weekday() == Thursday) {
state = boost::mp11::mp_find<boost::mp11::mp_list<typename TTransitions::src...>,
typename TTransition::dst>{};
return true;
}
}
}
return false;
}.template operator()<TTransitions>() or ...);
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
policy.template operator()<Transitions...>(msg, state_);
return state_;
}
constexpr explicit(false) sm(const Transitions&...) {}
private:
std::size_t state_{};
};
constexpr auto example_policy = []<class... Ts>(const auto& msg, auto& state){
using Msgs = mp_list<typename Ts::msg...>;
using MsgI = mp_find<Msgs, std::remove_cvref_t<decltype(msg)>>;
if constexpr (mp_less<MsgI, mp_size<Msgs>>::value) {
mp_with_index<sizeof...(Ts)>(state, [&]<class SrcI>(const SrcI&){
if constexpr (mp_same<SrcI, MsgI>::value) {
using Srcs = mp_list<typename Ts::src...>;
using Dsts = mp_list<typename Ts::dst...>;
using DstI = mp_at<Dsts, MsgI>;
state = mp_find<Srcs, DstI>::value;
}
});
}
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
policy.template operator()<Transitions...>(msg, state_);
return state_;
}
constexpr explicit(false) sm(const Transitions&...) {}
private:
std::size_t state_{};
};
constexpr auto example_policy = []<class... Ts, class T>(T const & msg, auto & state){
return mp_with_index<sizeof...(Ts)>( state
, [&]( auto I ) {
using TMsg = mp_at_c<mp_list<typename Ts::msg...>,I>;
using TDst = mp_at_c<mp_list<typename Ts::dst...>,I>;
return std::is_same_v<T,TMsg>? mp_find<mp_list<typename Ts::src...>, TDst>{}: state;
});
};
template <class... Transitions>
struct sm {
template <class TMsg>
constexpr auto process(const TMsg& msg,
dispatch_policy<TMsg, Transitions...> auto policy) {
state_ = policy.template operator()<Transitions...> (msg,state_);
return state_;
}
constexpr explicit(false) sm(const Transitions&...) {}
private:
std::size_t state_{};
};