FAQ
Limitations
on_entry<_> across translation units
When on_entry<_> (the wildcard entry handler) is defined in one translation unit
while the initial-event specialization is left undefined, the linker reports an
undefined reference. Make sure the entry handler is fully defined where it is used.
Dispatch priority: a specific on_entry<e1> handler takes priority over the
on_entry<_> wildcard. The wildcard fires only when no specific handler matches the
event used to enter the state.
// on_entry<e1> fires when entering via e1 (specific handler wins)
// on_entry<_> fires when entering via any other event (no specific handler)
operator, with two raw member-function-pointer actions
Two raw member-function-pointer actions combined with the comma operator
(&A::f, &A::g) select the built-in comma operator, because both operands are
class types — so SML's action-sequencing is bypassed. Wrap the actions with
wrap(...) to restore sequencing:
*"s1"_s + event<e> / (wrap(&A::f), wrap(&A::g)) = X
See example/actions_guards.cpp for a worked example.
Frequently asked questions
How do I get / inspect the current state(s)?
Use sm.is(...) to test the active state, or sm.visit_current_states(...) to
iterate them. state.c_str() gives the state name:
assert(sm.is("idle"_s));
sm.visit_current_states([](auto state) { std::cout << state.c_str() << '\n'; });
How do I check whether the machine is in one of several states?
is(...) accepts one state per orthogonal region, so all regions are tested in a
single call (no need to release a held mutex between checks):
sm.is(s1, s2); // region 0 == s1 AND region 1 == s2
How do I know whether an event was handled? Can I pass an event instance?
process_event returns bool — true when the event was handled, false
otherwise (an unhandled event triggers unexpected_event<E>). You can pass either a
type or an instance:
const bool handled = sm.process_event(my_event{42});
How do I dispatch events from another thread or an async handler?
Configure the machine with a process_queue / defer_queue policy and call
sm.flush_queue() to drain events that were pushed after process_event returned
(for example from an asynchronous callback). flush_queue() is a no-op when empty.
How do I discard deferred events?
Use the sml::clear_defer action in a transition to drop events that were deferred
earlier.
Is SML part of Boost?
[Boost].SML is a standalone, single-header library with no dependencies —
neither STL nor Boost are required. It is part of the boost-ext project; it is not
(currently) an official Boost library, and you do not need Boost to use it.
Is SML a Moore or a Mealy state machine?
Both. Transition actions run on transitions (Mealy), while on_entry<...> /
on_exit<...> provide per-state actions (Moore).
Can a state hold data?
Yes. A state is a plain type, take it by reference in a guard or action to read or
change it. example/data.cpp keeps an id in the Connected and Interrupted
states and copies it between them:
const auto set = [](const auto& event, Connected& state) { state.id = event.id; };
const auto update = [](Connected& src_state, Interrupted& dst_state) { dst_state.id = src_state.id; };
Is a state machine thread safe? Can I call process_event from an action?
Not by default. Pass the thread_safe policy with a lock type. With
std::recursive_mutex, process_event can be called from inside an action:
sml::sm<example, sml::thread_safe<std::recursive_mutex>> sm;
How do I handle any event (wildcard)?
Use both event<_> and unexpected_event<_>. "s"_s + event<_> / action matches every event type that appears somewhere in the table, "s"_s + unexpected_event<_> / action the types that do not. A specific event<e> row on the same state has priority over the wildcard.
Why doesn't unexpected_event<_> catch an event that is in the table?
unexpected_event<_> matches only event types that appear nowhere in the table. A table event with no transition in the current state is dropped and process_event returns false. To react to it add unexpected_event<that_event>, or check the returned bool.
How do I handle an event in every state, with a default action?
Use state<_> as the source. It applies in every state with no transition of its own for the event, so a specific row such as "s2"_s + event<e> / specific wins there and the default does not run:
"s2"_s + event<e> / specific,
state<_> + event<e> / default_action
Why is my "name"_e event not handled?
"name"_e is the event token for tables. To send it, create an instance with parentheses: sm.process_event("name"_e()). Without them it compiles, nothing matches and process_event returns false.
How do I check the state of a sub-state-machine?
Name the sub machine's state type in the first template argument of is:
sm.is<decltype(sml::state<sub>)>(sml::state<sub_state>);
sm.is(sml::state<sub>) only says the parent is inside sub. For "any of these states" (what MSM calls a flag) combine is calls:
template <class SM, class... S>
bool is_any(const SM& sm, S... s) { return (sm.is(s) || ...); }
How do I enter a composite state in a specific sub-state?
There is no direct entry: state<Composite::Sub2> is treated as a state of the parent and skips the composite's on_entry. Transition to state<Composite> and give the composite an initial dispatch state with guarded anonymous transitions to the sub-states, driven by a dependency the parent sets first.
How do I reuse a base transition table in several machines?
make_transition_table takes transitions, not other tables. Return the shared transitions in a std::tuple and unpack them with std::apply (C++17):
struct base { static constexpr auto transitions() { return std::make_tuple(*"a"_s + event<e> = "b"_s); } };
struct derived { auto operator()() const { return std::apply([](auto... t) {
return sml::make_transition_table(t..., "b"_s + event<f> = "c"_s); }, base::transitions()); } };
This is reuse, a base transition cannot be overridden.
Can I build the transition table conditionally or in pieces?
Not at runtime and not by concatenating tables, the table is a type. Select a whole table with if constexpr on a template parameter, or use guards for runtime conditions. For the same state and event, transitions are tried in written order and the first one whose guard passes runs.
Can I ask whether an event would be handled without processing it?
Not directly, there is no can_process_event. Processing a copy (auto probe = sm; probe.process_event(e)) answers it including guards, but actions run and dependencies passed by reference are shared with the copy. Use it only when actions have no outside side effects.
How do I ignore events while an action is in progress?
Make the in-progress period a state with one way out: idle + event<start> / begin_work = busy and busy + event<work_done> = idle. Events with no transition in busy are dropped. Send work_done when the work completes, or queue it from the action with process(work_done{}) and a process_queue policy.
How do I process a new event built from the incoming one?
Use an action taking back::process<E> (needs a process_queue policy):
event<raw> / [](const raw& e, back::process<converted> process) { process(converted{e.value * 2}); }
How do errors reach the caller of process_event?
process_event returns only bool. With no exception<> transition in the table, exceptions propagate to the caller. With any exception<> transition, unmatched exceptions are swallowed. To report one, store it in a dependency:
"s"_s + exception<_> / [](std::exception_ptr& e) { e = std::current_exception(); } = "idle"_s
Without exceptions, report an error from a sub-state by firing an event from the action with process(request_failed{}) (needs a process_queue policy). Events the sub machine does not handle fall through to the parent.
How do I read my context in a logger? Why is the initial substate not logged?
A logger is a dependency like any other: give it a reference to your context and pass both to the machine, sml::sm<top, sml::logger<my_logger>> sm{logger, ctx}. log_state_change gets only name holders (c_str()), not state objects, and runs on transitions only, before the destination's on_entry. The initial state of a sub machine is entered without a transition, log it from an on_entry<_> action on that state.
How do I see the current state in a debugger?
The sm object holds template internals, not names. Track the name with a logger policy and watch the variable:
template <class SM, class TSrc, class TDst>
void log_state_change(const TSrc&, const TDst& dst) { current_state_name = dst.c_str(); }
The logger must define all four log_* members and be passed as an lvalue to the sm constructor.
Why does wrap(&Class::method) fail with "cannot bind non-const lvalue reference"?
A member-function pointer makes Class a dependency of the machine and dependencies are held by reference. Pass an lvalue of Class to the sm constructor, a temporary will not bind.
Why can't I pass a temporary dependency to the sm constructor?
Dependencies are held by reference, so sm{logger{}} does not compile when an action or the logger takes logger&. Pass an lvalue that outlives the machine, or define BOOST_SML_CREATE_DEFAULT_CONSTRUCTIBLE_DEPS to make the machine keep its own copy of a default-constructible dependency.