boost-ext / sml

C++14 State Machine library
https://boost-ext.github.io/sml
Boost Software License 1.0
1.16k stars 179 forks source link

copy of class containing transition table is made when boost::sml::sm is instantiated #607

Closed devzeb closed 10 months ago

devzeb commented 10 months ago

Expected Behavior

Given this code:

#include <boost/sml.hpp>
#include <iostream>

namespace sml = boost::sml;

namespace {
    struct event1 {};

    struct StateMachine {
        StateMachine() = default;
        StateMachine(const StateMachine &) = delete;

        auto operator()() {
            using namespace sml;
            return make_transition_table(
                * "start"_s + event<event1> / [](const StateMachine& instance) { std::cout << instance.a;} = "finish"_s
                );
        }

        int a = 0;
    };
} // namespace

int main() {
    StateMachine stateMachineInstance;
    sml::sm<StateMachine> sm{
        static_cast<StateMachine &>(stateMachineInstance)
    };
}

I expect the reference to stateMachineInstance to be used as the dependency whenever it is required. This includes any internal dependency that boost::sml might have on an instance of StateMachine and also for the parameter of the lambda expression.

Also, the behavior is unexpected, because any modifications of the object, passed in as the reference, is not actually considered by the state machine. This is because boost::sml::sm uses it's internal copy of StateMachine instead of the reference passed in.

An example of this is when using a lambda expression that captures [this](){...} e.g. as an action like so:

struct StateMachine {
    StateMachine() = default;
    StateMachine(const StateMachine &) = delete;

    auto operator()() {
        using namespace sml;
        return make_transition_table(
            * "start"_s + event<event1> / [this]() { std::cout << privateMemberVariable;} = "finish"_s
            );
    }

    int privateMemberVariable = 0;
};

Why do I also want this behavior?

This would allow the boost::sml::sm to be a member of a class, that subclasses StateMachine.

class ClassWithStateMachine final : StateMachine {
    boost::sml::sm<StateMachine> sm{
            static_cast<StateMachine&>(*this)
    };

Doing it this way, it greatly improves the usability in an object oriented context, e.g. when only ClassWithStateMachine should be able to access protected variables of StateMachine or for testing.

Actual Behavior

boost::sml is internally trying to make a copy of StateMachine from the reference StateMachine &, which fails because the copy constructor is deleted.

The error message is the following:

In file included from /home/seb/code/example_use_boost_sml_in_class/main2.cpp:1:
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:447:23: error: call to deleted constructor of '(anonymous namespace)::StateMachine'
      : pool_type<Ts>(try_get<aux::remove_const_t<aux::remove_reference_t<aux::remove_pointer_t<Ts>>>>(&p))... {}
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:1755:41: note: in instantiation of function template specialization 'boost::sml::aux::pool<(anonymous namespace)::StateMachine>::pool<(anonymous namespace)::StateMachine &>' requested here
  constexpr explicit sm(TDeps &&deps) : deps_{aux::init{}, aux::pool<TDeps>{deps}}, sub_sms_{aux::pool<TDeps>{deps}} {
                                        ^
/home/seb/code/example_use_boost_sml_in_class/main2.cpp:26:27: note: in instantiation of function template specialization 'boost::sml::back::sm<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>::sm<(anonymous namespace)::StateMachine &, 0>' requested here
    sml::sm<StateMachine> sm{
                          ^
/home/seb/code/example_use_boost_sml_in_class/main2.cpp:11:9: note: 'StateMachine' has been explicitly marked deleted here
        StateMachine(const StateMachine &) = delete;
        ^
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:361:39: note: passing argument to parameter 'object' here
  constexpr explicit pool_type_impl(T object) : value{object} {}
                                      ^
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:1421:56: error: call to deleted constructor of 'aux::conditional_t<aux::is_empty<typename sm_policy<StateMachine>::sm>::value, aux::none_type, typename sm_policy<StateMachine>::sm>' (aka '(anonymous namespace)::StateMachine')
  constexpr sm_impl(const TPool &p, aux::false_type) : sm_t{aux::try_get<sm_t>(&p)}, transitions_{(*this)()} {
                                                       ^   ~~~~~~~~~~~~~~~~~~~~~~~~
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:1419:50: note: in instantiation of function template specialization 'boost::sml::back::sm_impl<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>::sm_impl<boost::sml::aux::pool<(anonymous namespace)::StateMachine &>>' requested here
  constexpr sm_impl(aux::init, const TPool &p) : sm_impl{p, aux::is_empty<sm_t>{}} {}
                                                 ^
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:363:54: note: in instantiation of function template specialization 'boost::sml::back::sm_impl<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>::sm_impl<boost::sml::aux::pool<(anonymous namespace)::StateMachine &>>' requested here
  constexpr pool_type_impl(init i, TObject object) : value{i, object} {}
                                                     ^
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:449:45: note: in instantiation of function template specialization 'boost::sml::aux::pool_type_impl<boost::sml::back::sm_impl<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>>::pool_type_impl<boost::sml::aux::pool<(anonymous namespace)::StateMachine &>>' requested here
  constexpr pool(const pool<TArgs...> &p) : pool_type<Ts>(init{}, p)... {}
                                            ^
/home/seb/code/example_use_boost_sml_in_class/external/sml/include/boost/sml.hpp:1755:85: note: in instantiation of function template specialization 'boost::sml::aux::pool<boost::sml::back::sm_impl<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>>::pool<(anonymous namespace)::StateMachine &>' requested here
  constexpr explicit sm(TDeps &&deps) : deps_{aux::init{}, aux::pool<TDeps>{deps}}, sub_sms_{aux::pool<TDeps>{deps}} {
                                                                                    ^
/home/seb/code/example_use_boost_sml_in_class/main2.cpp:26:27: note: in instantiation of function template specialization 'boost::sml::back::sm<boost::sml::back::sm_policy<(anonymous namespace)::StateMachine>>::sm<(anonymous namespace)::StateMachine &, 0>' requested here
    sml::sm<StateMachine> sm{
                          ^
/home/seb/code/example_use_boost_sml_in_class/main2.cpp:11:9: note: 'StateMachine' has been explicitly marked deleted here
        StateMachine(const StateMachine &) = delete;
        ^

Steps to Reproduce the Problem

Compile the source code above with boost::sml v1.1.9

Specifications

devzeb commented 10 months ago

Solution: use new policy boost::sml::sm<StateMachine, sml::dont_instantiate_statemachine_class>