I know there are several implementation of FSM in C++ containing different set of features and written in different styles. Nevertheless, I've decided to write my own.
Here are some key points :
- c++17 (not later)
- no need for the state.entry / state.exit functionality
- number of states ≤ 10
- I find it quite convenient to represent FSM as a class when states and transitions have access to data members
fsm.hpp
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
#include <type_traits>
template<class TMachine, typename TEvent, typename TState, TState initial>
struct FSM
{
static_assert(std::is_enum<TEvent>::value);
static_assert(std::is_enum<TState>::value);
typedef bool (TMachine::*EventAction_t)();
typedef void (TMachine::*TransitionAction_t)();
struct Transition
{
TState from;
TEvent event;
TState to;
TransitionAction_t action { &TMachine::noAction };
};
typedef std::map<TState, EventAction_t> StateMap;
typedef std::vector<Transition> TransitionList;
void process(const TEvent event)
{
auto it = std::find_if(transitions.begin(), transitions.end(),
[event, this](const auto& tr)
{
return tr.event == event and tr.from == currentState;
});
if(it == transitions.end())
return;
(static_cast<TMachine*>(this)->*(it->action))();
currentState = it->to;
}
bool step()
{
if(states.find(currentState) == states.end())
return false;
return (static_cast<TMachine*>(this)->*states[currentState])();
}
void noAction()
{
std::cout << "No action\n"; // debug
}
static TransitionList transitions;
static StateMap states;
TState currentState { initial };
};
Usage
Let's implement the coffee machine :
#include <iostream>
#include <cstdint>
#include "fsm.hpp"
enum class State_t : uint8_t
{
eIdle,
eWait4Money,
eWait4Coffee,
ePowerOff,
};
enum class Event_t : uint8_t
{
eMoneyInserted,
eCoffeeButtonPressed,
eTimeoutElapsed,
ePowerOff,
};
struct CoffeeMachine : public FSM<CoffeeMachine, Event_t, State_t, State_t::eIdle>
{
bool onIdle()
{
std::cout << "FSM :: I'm waiting for a new customer ($ earned: " << moneyEarned << ")\n";
return true;
}
bool onWait4Money()
{
std::cout << "FSM :: Insert money, please\n";
return true;
}
bool onWait4Coffee()
{
std::cout << "FSM :: Choose a beverage, please\n";
return true;
}
bool onPowerOff()
{
std::cout << "FSM :: I need some sleep, goodbye...\n";
return false;
}
void onMoneyInserted()
{
moneyEarned++;
}
unsigned int moneyEarned = 0;
};
template<>
CoffeeMachine::StateMap FSM<CoffeeMachine, Event_t, State_t, State_t::eIdle>::states =
{
{ State_t::eIdle, &CoffeeMachine::onIdle },
{ State_t::eWait4Money, &CoffeeMachine::onWait4Money },
{ State_t::eWait4Coffee, &CoffeeMachine::onWait4Coffee },
{ State_t::ePowerOff, &CoffeeMachine::onPowerOff },
};
template<>
CoffeeMachine::TransitionList FSM<CoffeeMachine, Event_t, State_t, State_t::eIdle>::transitions = {
// From // Event // To // Action
{ State_t::eIdle, Event_t::eCoffeeButtonPressed, State_t::eWait4Money },
{ State_t::eIdle, Event_t::ePowerOff, State_t::ePowerOff },
{ State_t::eWait4Money, Event_t::ePowerOff, State_t::ePowerOff },
{ State_t::eWait4Money, Event_t::eMoneyInserted, State_t::eWait4Coffee, &CoffeeMachine::onMoneyInserted },
{ State_t::eWait4Money, Event_t::eTimeoutElapsed, State_t::eIdle },
{ State_t::eWait4Coffee, Event_t::eCoffeeButtonPressed, State_t::eIdle },
{ State_t::eWait4Coffee, Event_t::eTimeoutElapsed, State_t::eIdle },
};
int main()
{
CoffeeMachine fsm;
while(fsm.step())
{
std::cout << "Choose your destiny [p - power, m - money, c - coffee] : ";
char input = 't';
std::cin >> input;
Event_t e = Event_t::eTimeoutElapsed;
switch(input)
{
case 'p' :
e = Event_t::ePowerOff;
break;
case 'm' :
e = Event_t::eMoneyInserted;
break;
case 'c' :
e = Event_t::eCoffeeButtonPressed;
break;
default :
e = Event_t::eTimeoutElapsed;
break;
}
fsm.process(e);
}
return 0;
}
I don't like the boilerplate of the specialization of the state and transition list, but I cannot see how I could do it less verbose. So what do you think?
