Info
-
Did you know that mapping types to values is a simple way to transition from compile-time to run-time space?
Example
template<class T, class... Ts>
constexpr auto find = ([] {
if constexpr (std::is_same_v<T, typename Ts::second_type>) {
return Ts::first_type::value;
} else {
return 0;
}
}() + ...);
struct T0{};
struct T1{};
struct T2{};
static_assert(0 == find<T0, std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>);
static_assert(1 == find<T1, std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>);
static_assert(2 == find<T2, std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>);
Puzzle
- Can you implement a simple
State machine
which returns the current state with theprocess
call?
template<class Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
/*TODO*/return 0;
}
};
template<class TSrc, class TMsg, class TDst>
struct transition {
using src = TSrc;
using msg = TMsg;
using dst = TDst;
};
struct msg0{};
struct msg1{};
struct msg2{};
int main() {
using namespace boost::ut;
"state machine"_test = [] {
sm sm{
std::tuple{
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{}));
expect(0_i == sm.process(msg2{}));
};
should("transition to destination state on expected message") = [&] {
expect(1_i == sm.process(msg1{}));
};
should("stay in the same state on unexpected message") = [&] {
expect(1_i == sm.process(msg0{}));
expect(1_i == sm.process(msg1{}));
};
should("transition to source state on expected message") = [&] {
expect(0_i == sm.process(msg2{}));
};
};
}
Solutions
template<class Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&) {}
constexpr static std::size_t N = std::tuple_size_v<Transitions>;
template<class TMsg>
constexpr auto process(const TMsg&) {
[&]<auto ... Is >( std::index_sequence<Is...> const & )
{
( [&](){ using T = std::tuple_element_t<Is,Transitions>;
if( Is == current_state && std::is_same_v< typename T::msg , TMsg> )
{
// assuming we can find a state index that the src match this dst
std::size_t next_state = []<auto ... Js >( std::index_sequence<Js...> const & ){
return ( [](){ if(std::is_same_v< typename std::tuple_element_t<Js,Transitions>::src, typename T::dst>)
return Js;
return 0ul;
}() + ...);
}( std::make_index_sequence<N>{});
current_state = next_state;
}
}(), ...);
}(std::make_index_sequence<N>{});
return current_state;
}
std::size_t current_state = 0ul;
};
template<class... Ts>
struct sm {
using states_t = boost::mp11::mp_unique<boost::mp11::mp_list<typename Ts::src..., typename Ts::dst...>>;
template<class TMsg>
constexpr auto process(const TMsg& msg) {
([this] {
if constexpr (std::is_same_v<TMsg, typename Ts::msg>) {
if (state_ == boost::mp11::mp_find<states_t, typename Ts::src>{}) {
state_ = boost::mp11::mp_find<states_t, typename Ts::dst>{};
}
}
}(), ...);
return state_;
}
constexpr explicit(false) sm(const std::tuple<Ts...>&) {}
private:
int state_{};
};
template<class Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
std::visit([this]<class I>(const I&) {
using Prev = mp_at<Transitions, I>;
if constexpr (is_trait_same<Prev, mp_quote<msg>, TMsg>::value) {
using is_src_same_prev_dst = mp_bind_back<is_trait_same, mp_quote<src>, dst<Prev>>;
using J = mp_find_if_q<Transitions, is_src_same_prev_dst>;
using Next = mp_bind_front<mp_at, Transitions, J>;
if constexpr (mp_valid_q<Next>::value) {
state = mp_find<Transitions, mp_invoke_q<Next>>{};
}
}
}, state);
return state.index();
}
private:
template<class T>
using src = typename T::src;
template<class T>
using msg = typename T::msg;
template<class T>
using dst = typename T::dst;
template<class T, class Trait, class V>
using is_trait_same = mp_same<mp_eval_or_q<void, Trait, T>, V>;
using State = mp_rename<mp_iota<mp_size<Transitions>>, std::variant>;
State state{mp_size_t<0>{}};
};
namespace sm_impl {
template<class ...CS> struct set{};
template<class ...Cs> struct list{};
template<typename N, typename ... C>
struct is_in {
static constexpr bool value {(std::is_same_v<N, C> || ...)};
};
template<class S, class L> struct add_to_set;
template<class ...Cs, class N>
struct add_to_set<set<Cs...>, list<N>> {
using type = std::conditional_t< is_in<N,Cs...>::value, set<Cs...>, set<Cs...,N>>;
};
template<class... Cs, class N, class ...Ns>
struct add_to_set< set<Cs...>, list<N,Ns...> > {
using type = std::conditional_t< is_in<N, Cs...>::value,
typename add_to_set< set<Cs...>, list<Ns...>>::type,
typename add_to_set< set<Cs...,N>, list<Ns...>>::type>;
};
template<class S, class T, class IC> struct index_of_impl;
template<class T, int N>
struct index_of_impl<set<>, T, std::integral_constant<int, N>> {
using type = std::integral_constant<int, -1>;
};
template<class C, class ...Cs, class T, int N>
struct index_of_impl<set<C, Cs...>, T, std::integral_constant<int, N>> {
using type = std::conditional_t< std::is_same_v<C,T>,
std::integral_constant<int, N>,
typename index_of_impl< set<Cs...>, T, std::integral_constant<int, N+1>>::type>;
};
template<class S, class T> struct index_of;
template<class ... Cs, class T> struct index_of<set<Cs...>, T> {
using type = typename index_of_impl<set<Cs...>, T, std::integral_constant<int, 0> >::type;
};
template<class Src, class Msg, class Dst> struct instruction{};
template<typename Transitions> struct TransitionList;
template<template<typename> typename C, template<class, class, class> typename T,
typename ... Src, typename ... Msg, typename ... Dst>
struct TransitionList< C<T<Src, Msg, Dst>...>> {
using stateSet = add_to_set<set<>, list<Src..., Dst...>>::type;
using type = std::tuple< instruction< typename index_of<stateSet, Src>::type,
Msg,
typename index_of<stateSet, Dst>::type> ...>;
};
template<int... S, class msg, class ...Msgs, int... D>
int process(int currState, msg const&, std::tuple< instruction<std::integral_constant<int, S>, Msgs, std::integral_constant<int, D>>...> const& ) {
int newState = currState;
([&newState, currState= currState](){
if ( currState == S && std::is_same_v<msg,Msgs>)
newState = D;
}() , ...);
return newState;
}
} //namespace sm_impl
template<class Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
using transitionList = typename sm_impl::TransitionList<Transitions>::type;
state = sm_impl::process(state, TMsg{}, transitionList{});
return state;
}
int state = 0;
};
template<template <class...> class TList, class... Ts>
struct sm {
constexpr explicit(false) sm(TList<Ts...>&&) {}
template<class TMsg>
constexpr auto process(const TMsg& m) {
([&] {
if constexpr (std::is_same_v<TMsg, typename Ts::msg>) {
if (index_for<typename Ts::src>() == current_state) {
current_state = index_for<typename Ts::dst>();
return true;
}
}
return false;
}() or ...);
return current_state;
}
private:
template <class TMsg>
consteval static auto index_for() {
return [] <class T, T... Is> (std::integer_sequence<T, Is...>) {
return ((std::is_same_v<TMsg, typename Ts::src> ? Is : 0) + ... + 0);
}(std::make_integer_sequence<int, sizeof...(Ts)>{});
}
int current_state{};
};
template<class Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&) {}
template <class TDst, typename T, T... ints>
constexpr auto dest_index(std::integer_sequence<T, ints...> index_seq){
return ([](){
using element_type = std::tuple_element_t<ints, Transitions>;
if (std::is_same_v<TDst, typename element_type::src>)
{
return static_cast<int>(ints);
}
else
return 0;
}() + ...);
}
template<class TMsg, class TIndex>
constexpr auto matched()
{
constexpr auto transitions_size = std::tuple_size<Transitions>::value;
using index_seq = std::make_index_sequence<transitions_size>;
using element_type = std::tuple_element_t<TIndex::value, Transitions>;
if (state == TIndex::value && std::is_same_v<TMsg, typename element_type::msg>)
return dest_index<typename element_type::dst>(index_seq{}) - TIndex::value;
else
return 0;
}
template<class TMsg, typename T, T... ints>
constexpr auto sum_matches(std::integer_sequence<T, ints...> index_seq)
{
return (matched<TMsg, std::integral_constant<int, ints>>() + ...);
}
template<class TMsg>
constexpr auto process(const TMsg&) {
constexpr auto transitions_size = std::tuple_size<Transitions>::value;
using index_seq = std::make_index_sequence<transitions_size>;
auto state_step = sum_matches<TMsg>(index_seq{});
state += state_step;
return state;
}
private:
int state = 0;
};