Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added policy that controls if the transition table class is instantiated #608

Conversation

devzeb
Copy link
Contributor

@devzeb devzeb commented Jan 14, 2024

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.

struct sm_impl : aux::conditional_t<aux::is_empty<typename TSM::sm>::value, aux::none_type, typename TSM::sm> {...}

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.

Effects:

The following code is not able to compile:

sml::sm<StateMachine, sml::dont_instantiate_statemachine_class> sm{};

This yields the following error message:

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).

Issue: #607

Reviewers:
@

…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
@kris-jusiak kris-jusiak merged commit b885766 into boost-ext:master Jan 14, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants