I am working on a finite state machine library, with these public methods:
template <typename STATE_T>
void add_state(); // allocate STATE_T on heap - STATE_T must derive from state
template <typename FROM_STATE_T, typename TO_STATE_T>
void link_state(std::function<bool()> cond, unsigned int priority);
// search for base state pointer that can be dynamically caste d to STATE_T* type, set this state as the beginning state
template <typename STATE_T>
void begin_state();
void execute();
//STATE_T must be a user defined state class that inherits from the base state class.
I chose a run-time implementation over compile time, because the interface would be more complicated when using variadic template parameters for states. However, there are constraints that I want to enforce to make sure the programmer does not introduce bugs when implementing their state machine.
Here are the constraints I want to enforce:
- Cannot add duplicate states (ie. adding two of the same derived type)
- Cannot link states that do not exist (such as a state type not in the fsm list of states)
- States must have an entry point/be reachable
- A begin and end state must exist
- execute cannot be run multiple times concurrently
Assertion aborts the program and clearly adheres to these constraints, but is it the correct choice?
It is possible for 1,2,3 to be violated and the fsm still be in a valid state (simply do nothing) but I dont like the idea of implicitly handling these, because it introduces a false sense of security by hiding programmer faults.
If I throw exceptions for 1,2,3 and the programmer catches them, then fsm could still be in a valid state, allowing an ill-formed fsm to run.
5 is something that should not be done. should I handle this, or leave it as UB?
What would be the most appropriate mechanism to use for these constraints?