Added dont_instantiate_statemachine_class policy: when supplied, don't subclass the transition table type and require a reference to an instance of that type in the constructor of sml::sm and all sub-statemachine types.
Problem:
A non-empty transition table class is always instantiated by boost::sml::sm.
This is because sm_impl is a subclass of the transition table class whenever the class is non-empty.
When the transition table class has a non default constructor, it is required to supply a reference to an instance of this class in the constructor of boost::sml::sm.
This leads to confusion, when a reference to the instance of a transition table is supplied as a constructor parameter, which is then modified after the creation of the state machine:
struct e1 {};
struct StateMachine {
explicit StateMachine(int a) { }
auto operator()() {
using namespace sml;
return make_transition_table(
// clang-format off
*"start"_s + event<e1>/ [this]() {
std::cout << "member_variable: " << member_variable << '\n';
}
= "end"_s
// clang-format on
);
}
int member_variable = 0;
};
StateMachine transition_table_class_instance{0};
sml::sm<StateMachine> sm{
transition_table_class_instance // copy is made here
};
transition_table_class_instance.member_variable = 42; // this change of the member variable is not reflected in the state machine
sm.process_event(e1{});
This prints:
member_variable: 0
It is not apparent that the assignment of transition_table_class_instance.member_variable does not change the internal state of the state machine, because the normal user does not know that the class is copied.
Solution:
I added a new policy called dont_instantiate_statemachine_class (new policy to not break existing implementations).
When applied, no transition table class from either the base or sub state machines are instantiated and sm_impl is not a subclass of the type (like in the current implementation when the transition table class is considered "empty").
Instead, it is required to supply references to instances of these classes as a parameter to boost::sml::sm.
static assertion failed due to requirement '!(should_not_instantiate_statemachine_class<boost::sml::back::sm_policy<StateMachine, boost::sml::back::policies::dont_instantiate_statemachine_class>>::value && aux::would_instantiate_missing_ctor_parameter())':
When policy sml::dont_instantiate_statemachine_class is used, you have to provide a reference to an instance of the transition table type (boost::sml::sm< your_transition_table_type >) as well as a reference to instances of all sub-statemachine types as constructor parameters.
The previous code example :
StateMachine transition_table_class_instance{0};
sml::sm<StateMachine, sml::dont_instantiate_statemachine_class> sm{
transition_table_class_instance // no copy is made here
};
transition_table_class_instance.member_variable = 42; // this changes the member variable in the state machine
sm.process_event(e1{});
Now prints:
member_variable: 42
This also enables the state machine to be used in a more object oriented use case like this:
class ClassWithStateMachine final : StateMachine {
boost::sml::sm<StateMachine, sml::dont_instantiate_statemachine_class> sm{
static_cast<StateMachine&>(*this)
};
}
This way, the subclass of the transition table ClassWithStateMachine can easily access and modify protected members of the state machine, that should not be accessible to others (information hiding).
Added dont_instantiate_statemachine_class policy: when supplied, don't subclass the transition table type and require a reference to an instance of that type in the constructor of sml::sm and all sub-statemachine types.
Problem:
A non-empty transition table class is always instantiated by boost::sml::sm. This is because
sm_impl
is a subclass of the transition table class whenever the class is non-empty.When the transition table class has a non default constructor, it is required to supply a reference to an instance of this class in the constructor of
boost::sml::sm
. This leads to confusion, when a reference to the instance of a transition table is supplied as a constructor parameter, which is then modified after the creation of the state machine:This prints:
It is not apparent that the assignment of
transition_table_class_instance.member_variable
does not change the internal state of the state machine, because the normal user does not know that the class is copied.Solution:
I added a new policy called
dont_instantiate_statemachine_class
(new policy to not break existing implementations). When applied, no transition table class from either the base or sub state machines are instantiated andsm_impl
is not a subclass of the type (like in the current implementation when the transition table class is considered "empty"). Instead, it is required to supply references to instances of these classes as a parameter toboost::sml::sm
.Effects:
The following code is not able to compile:
This yields the following error message:
The previous code example :
Now prints:
This also enables the state machine to be used in a more object oriented use case like this:
This way, the subclass of the transition table
ClassWithStateMachine
can easily access and modify protected members of the state machine, that should not be accessible to others (information hiding).Issue: #607
Reviewers: @