0
\$\begingroup\$

I'm writing a finite state machine based on a transition table in C++ 14. My code so far looks like:

#include <array>
#include <functional>

template < typename _State, typename _Event >
class FSM {
  static constexpr size_t state_count = static_cast<size_t>(_Event::_);
  static constexpr size_t event_count = static_cast<size_t>(_State::_);

  using _FuncPtr = typename std::function<_State(_State)>;
  using _Table   = typename std::array< std::array< _FuncPtr, state_count >, event_count >;

  _State _current;
  _Table _transitions;

public:
  FSM(_State initial, _Table transitions) : _current(initial), _transitions(transitions) {};

  void operator()(const _Event& event) {
    _current = _transitions[static_cast<size_t>(event)][static_cast<size_t>(_current)](_current);
  }

  _State state() const { return _current; }
};

The usage looks like:

/*            States & Events            */
enum class State : char { Locked, Unlocked,    _ };
enum class Event : char { Pass, Coin,          _ };

/*              Transitions              */
State alarm(State current) {
  cout << "Alarm" << endl;
  return current;
}

State unlock(State current) {
  cout << "Unlock" << endl;
  return State::Unlocked;
}

State lock(State current) {
  cout << "Lock" << endl;
  return State::Locked;
}

State thankyou(State current) {
  cout << "Thankyou" << endl;
  return current;
}


void main() {
  auto fsm = FSM< State, Event >(State::Locked, {{
    // Locked    Unlocked
    {  alarm,    lock      },  // Pass
    {  unlock,   thankyou  }   // Coin
  }});

  // To switch states
  fsm(Event::Coin);
  fsm(Event::Pass);
}

Is there some way I can further simplify it?

\$\endgroup\$
  • \$\begingroup\$ I think you mixed up the order of dimensions in operator() \$\endgroup\$ – ratchet freak May 13 '16 at 9:16
  • \$\begingroup\$ The rows are the events, and the columns are the states, so I believe it should be right? I might have mixed up the dimensions in the array definition though \$\endgroup\$ – tobspr May 13 '16 at 9:34
  • \$\begingroup\$ @ratchetfreak Changed the array definition, you were right, I had the dimensions switched :) \$\endgroup\$ – tobspr May 13 '16 at 9:36
  • \$\begingroup\$ rows vs. columns only makes sense when you also define whether row major or column major. \$\endgroup\$ – ratchet freak May 13 '16 at 9:43
  • \$\begingroup\$ @ratchetfreak its row major \$\endgroup\$ – tobspr May 13 '16 at 13:09
3
\$\begingroup\$

I see a number of things that may help you improve your code.

Don't use leading underscores in names

Anything with a leading underscore is a reserved name in C++ (and in C). See this question for details.

Don't use void main()

The only function signatures allowed by the C++ standard both require an int return type. See this question for details.

Don't abuse using namespace std

Putting using namespace std at the top of every program is a bad habit that you'd do well to avoid. This sample doesn't explicitly have that, but it's implied by the way the usage code is written.

Fix the bug

Right now, the way the arrays are constructed and the way that they're used are reversed. Try implementing a state machine in which the number of states does not equal the number of events and you'll quickly see what I mean.

Rethink the interface

Right now, there is very little benefit in using the FSM class. All of the actual code to implement the finite state machine is done with freestanding functions rather than member functions and requires a pair of enum classes that each must have _ as the last named item. Further, the event that caused the transition isn't passed to the function -- it's implicit. All of this makes for a fragile design. Take the classic example of a vending machine that dispenses items for fifteen cents and that accepts only nickels (5 cents) and dimes (10 cents). We'd have something like this:

enum class State : char { Empty, Five, Ten, _ };
enum class Event : char { Nickel, Dime,   _ };

// Then the state machine
auto fsm = FSM< State, Event >(State::Empty, {{
    // Nickel       Dime
    { zeroToFive,  zeroToTen },    // Empty
    { fiveToTen,   fiveToFifteen},  // Five
    { tenToFifteen, tenToTwenty}   // Ten
}});

Now we still have to write six standalone functions to implement this! That's not very convenient. Worse, the argument each function gets isn't even used, so each function looks something like this:

State fiveToFifteen(State ) {
  std::cout << "got a dime, now have 15 cents\nHere's your item!\n";
  return State::Empty;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.