Info
-
Did you know about variadic aggregate initialization?
Example
template<class... Ts>
struct sm {
template<class T>
constexpr auto process() {
return std::array{
[] {
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(std::array{0, 0} == sm<std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>{}.process<T0>());
static_assert(std::array{1, 0} == sm<std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>{}.process<T1>());
static_assert(std::array{0, 2} == sm<std::pair<std::integral_constant<int, 1>, T1>, std::pair<std::integral_constant<int, 2>, T2>>{}.process<T2>());
Puzzle
-
Can you implement a
State machine
with support for orthogonal regions which returns current states with theprocess
call?
template<class... Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&...) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
/*TODO*/return std::array<int, sizeof...(Transitions)>{};
}
};
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 with orthogonal regions"_test = [] {
sm sm{
std::tuple{
transition<class State1, msg1, class State2>{},
transition<class State2, msg2, class State1>{},
},
std::tuple{
transition<class State1, msg3, class State2>{},
transition<class State2, msg4, class State1>{}
}
};
should("state in the same state on unexpected message") = [&] {
expect(std::array{0, 0} == sm.process(msg0{}));
expect(std::array{0, 0} == sm.process(msg2{}));
expect(std::array{0, 0} == sm.process(msg4{}));
};
should("transition to destination state on expected message") = [&] {
expect(std::array{1, 0} == sm.process(msg1{}));
expect(std::array{1, 1} == sm.process(msg3{}));
};
should("stay in the same state on unexpected message") = [&] {
expect(std::array{1, 1} == sm.process(msg0{}));
expect(std::array{1, 1} == sm.process(msg1{}));
expect(std::array{1, 1} == sm.process(msg3{}));
};
should("transition to source state on expected message") = [&] {
expect(std::array{0, 1} == sm.process(msg2{}));
expect(std::array{0, 0} == sm.process(msg4{}));
};
};
}
Solutions
template<class Transitions>
struct sm_single {
constexpr explicit(false) sm_single(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;
}
int current_state = 0;
};
template<class... Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&... transitions)
:sms{sm_single(transitions) ...}
{}
template<class TMsg>
constexpr auto process(const TMsg& msg ) {
return [&]<auto ... Is >( std::index_sequence<Is...> const & ){
return std::array<int, sizeof...(Transitions)>{std::get<Is>(sms).process(msg)...};
}(std::make_index_sequence<sizeof...(Transitions)>{});
}
std::tuple<sm_single<Transitions>...> sms;
};
namespace detail {
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... Ts>
struct sm : decltype(detail::sm{Ts{}})... {
template<class TMsg>
constexpr auto process(const TMsg& msg) {
return std::array{static_cast<decltype(detail::sm{Ts{}})&>(*this).process(msg)...};
}
constexpr explicit(false) sm(const Ts&... ts)
: decltype(detail::sm{Ts{}}){ts}...
{ }
};
template<class... Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&...) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
[&] <auto... Is> (std::index_sequence<Is...>) {
(process<TMsg, Is, Transitions>(), ...);
}(std::index_sequence_for<Transitions...>{});
return current_states;
}
private:
template <class TMsg, auto Index, class TTransitions>
constexpr auto process() {
[&] <template <class...> class TList, class... Ts> (TList<Ts...>) {
([&] {
if constexpr (std::is_same_v<TMsg, typename Ts::msg>) {
if (index_for<typename Ts::src, TTransitions>() == current_states[Index]) {
current_states[Index] = index_for<typename Ts::dst, TTransitions>();
return true;
}
}
return false;
}() or ...);
}(TTransitions{});
}
template <class TMsg, class TTransitions>
constexpr auto index_for() const {
return [] <template <class...> class TList, class... Ts, auto... Is> (
TList<Ts...>, std::index_sequence<Is...>) {
return ([] () -> int {
if constexpr (std::is_same_v<TMsg, typename Ts::src>) {
return Is;
} else {
return 0;
}
}() + ... + 0);
}(TTransitions{}, std::make_index_sequence<std::tuple_size_v<TTransitions>>{});
}
std::array<int, sizeof...(Transitions)> current_states{};
};
template<class... Transitions>
struct sm {
template<class ...Ts>
using states_impl_t = boost::mp11::mp_unique<boost::mp11::mp_list<typename Ts::src..., typename Ts::dst...>>;
using states_t = std::tuple<boost::mp11::mp_rename<Transitions, states_impl_t>...>;
using TransitionList = boost::mp11::mp_list<Transitions...>;
constexpr explicit(false) sm(const Transitions&...) {}
template<class TMsg>
constexpr auto process(const TMsg&) {
[this]<auto... Is>(std::index_sequence<Is...> const&) {
( [this]<class... Ts, auto I>(std::tuple<Ts...> const&, std::integral_constant<int, I>const&) {
( [this]() {
if constexpr (std::is_same_v<TMsg, typename Ts::msg>) {
if ( states[I] == boost::mp11::mp_find< boost::mp11::mp_at_c<states_t, I>, typename Ts::src>{}) {
states[I] = boost::mp11::mp_find<boost::mp11::mp_at_c<states_t, I>, typename Ts::dst>{};
}
}
} (), ...);
}( boost::mp11::mp_at_c<TransitionList, Is>{}, std::integral_constant<int, Is>{} ), ...);
}(std::make_index_sequence<sizeof...(Transitions)>{});
return states;
}
private:
std::array<int, sizeof...(Transitions)> states{};
};
template<class... Transitions>
struct sm {
constexpr explicit(false) sm(const Transitions&...) {}
template <class TDst, class TTransitions, typename T, T... ints>
constexpr auto dest_index(std::integer_sequence<T, ints...> index_seq){
return ([](){
using element_type = std::tuple_element_t<ints, TTransitions>;
if (std::is_same_v<TDst, typename element_type::src>)
{
return static_cast<int>(ints);
}
else
return 0;
}() + ...);
}
template<class TMsg, class TTransitions, class TIndex>
constexpr auto matched(int state)
{
constexpr auto transitions_size = std::tuple_size<TTransitions>::value;
using index_seq = std::make_index_sequence<transitions_size>;
using element_type = std::tuple_element_t<TIndex::value, TTransitions>;
if (state == TIndex::value && std::is_same_v<TMsg, typename element_type::msg>)
return dest_index<typename element_type::dst, TTransitions>(index_seq{}) - TIndex::value;
else
return 0;
}
template<class TMsg, class TTransitions, typename T, T... ints>
constexpr auto sum_matches(std::integer_sequence<T, ints...> index_seq, int state)
{
return (matched<TMsg, TTransitions, std::integral_constant<int, ints>>(state) + ...);
}
template<class TMsg, class TTransitions>
constexpr auto get_state_for_transition_table(int i)
{
auto state = states[i];
constexpr auto transitions_size = std::tuple_size<TTransitions>::value;
using index_seq = std::make_index_sequence<transitions_size>;
auto state_step = sum_matches<TMsg, TTransitions>(index_seq{}, state);
state += state_step;
return state;
}
template<class TMsg, class ...>
constexpr auto process(const TMsg&) {
std::ptrdiff_t i = 0;
(void(states[i++] = get_state_for_transition_table<TMsg, Transitions>(i)) , ...);
return states;
}
private:
std::array<int, sizeof...(Transitions)> states{};
};
template<class... Transitions>
struct sm : sm<Transitions>... {
constexpr explicit(false) sm(const Transitions&... t) : sm<Transitions>(t)... {}
template<class TMsg>
constexpr auto process(const TMsg& msg) {
return std::array{sm<Transitions>::process(msg)...};
}
};
template<class Transitions>
struct sm<Transitions> {
constexpr explicit(false) sm(const Transitions&) {}
template<class TMsg>
constexpr int 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>{}};
};