back11: Dropped transition depending on transition table row order
- Dominant language
- C++
- Stars
- 40
- Forks
- 59
- PR merge metrics
- No merged PRs in 30d
Description
Hi,
I have a state machine that transitions from state `s0 -> s2` upon receipt of one or more events, but there are two possible paths for reaching `s2`. If the context contains a set flag, the transition order must be `s0 -> s1 -> s2` - in this case 2 identical events are received, each must trigger a state transition. If the context flag is not set, a single event will be received, which must cause a transition from `s0 -> s2` in a single step.
I've found I can only get this to work if I define the `transition_table` rows in a specific order, the unconstrained `s1 -> s2` transition row must be defined prior to the constrained rows that define `s0 -> s1` and `s1 -> s2`. Here's the example code in compiler explorer (I'll also paste it below): https://godbolt.org/z/8h7P6dG6o I've tested this with Boost 1.91 also, result is the same.
Is there a way to make it work with the `s1 -> s2` row defined last? I find that ordering to be more intuitive than the one that works.
```c++
#include
#include
#include
#include
#include
#include
#include
#include
struct go_event {};
struct context
{
bool flagged = true;
};
struct is_flagged
{
template
bool operator()(Event const&, Fsm& fsm, SourceState&, TargetState&)
{
return fsm.ctx.flagged;
}
};
using not_flagged = boost::msm::front::euml::Not_;
struct s0 : boost::msm::front::state<> {};
struct s1 : boost::msm::front::state<> {};
struct s2 : boost::msm::front::state<> {};
using none = boost::msm::front::none;
// Working case: the s1 -> s2 unconditional transition is declared first
struct working_fsm_ : boost::msm::front::state_machine_def
{
context ctx;
bool no_transition_fired = false;
using initial_state = s0;
template
using row = boost::msm::front::Row;
struct transition_table
: boost::mpl::vector<
row,
row,
row
>
{};
template
void no_transition(Event const&, Fsm&, int)
{
no_transition_fired = true;
}
};
using working_fsm = boost::msm::back11::state_machine;
// Broken case: same as working, except s1 -> s2 transition is declared last,
// which is the more intuitive ordering
struct broken_fsm_ : boost::msm::front::state_machine_def
{
context ctx;
bool no_transition_fired = false;
using initial_state = s0;
template
using row = boost::msm::front::Row;
struct transition_table
: boost::mpl::vector<
row,
row,
row
>
{};
template
void no_transition(Event const&, Fsm&, int)
{
no_transition_fired = true;
}
};
using broken_fsm = boost::msm::back11::state_machine;
template
int current_state_id(Fsm const& fsm)
{
return *fsm.current_state();
}
int main()
{
std::printf("Boost version: %s\n", BOOST_LIB_VERSION);
// working_fsm: s0 -[flagged]-> s1 -> s2
{
working_fsm fsm;
fsm.start();
fsm.process_event(go_event{}); // expect s0 -> s1
int after_first = current_state_id(fsm);
fsm.process_event(go_event{}); // expect s1 -> s2
int after_second = current_state_id(fsm);
std::printf(
"[working] after 1st event: state_id=%d after 2nd event: state_id=%d "
"no_transition_fired=%d\n",
after_first,
after_second,
fsm.no_transition_fired);
if(fsm.no_transition_fired)
{
std::printf("[working] unexpected fail\n");
}
}
// --- broken_fsm: s0 -[flagged]-> s1 -> s1
{
broken_fsm fsm;
fsm.start();
fsm.process_event(go_event{}); // expect s0 -> s1
int after_first = current_state_id(fsm);
fsm.process_event(go_event{}); // expect s1 -> s2, but no_transition() is called
int after_second = current_state_id(fsm);
std::printf(
"[broken] after 1st event: state_id=%d after 2nd event: state_id=%d "
"no_transition_fired=%d\n",
after_first,
after_second,
fsm.no_transition_fired);
if(!fsm.no_transition_fired)
{
std::printf("[broken] Unexpected pass\n");
}
else
{
std::printf(
"[broken] Expected fail: transition from s1 -> s2 didn't happen\n");
}
}
}
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with boost/msm/back11/state_machine.hpp and the transition_table and Row entry points shown in the reproducer. Build and run the Compiler Explorer example, or an equivalent local case, with both transition-row orders; done means the s1 -> s2 transition reaches s2 in both orders without calling no_transition().
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100