boost-ext / sml

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

Can't declare state on forward declared submachine #278

Open etam opened 5 years ago

etam commented 5 years ago

I tried to hide the state machine implementation behind an interface and pimpl. It all works well for a simple state machine, but not for one with submachine.

MNWE (minimal not working example):

namespace sml = boost::sml;

// hpp

struct e1 {};
struct e2 {};

struct inner;
const auto idle = sml::state<class idle>;
const auto idle2 = sml::state<class idle2>;
const auto s1 = sml::state<class s1>;
const auto state_inner = sml::state<inner>;

// cpp

struct inner {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      *idle + event<e1> = s1
    );
  }
};

struct outer {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *idle2 + event<e2> = state_inner
    );
  }
};

int main() {
  sml::sm<outer, sml::testing> sm;
  assert(sm.is(idle2));
  sm.set_current_states(state_inner);
  sm.set_current_states<decltype(state_inner)>(s1);
  assert(sm.is(state_inner));
  assert(sm.is<decltype(state_inner)>(s1));
}

This fails to compile, because when state_inner is created, struct inner is only forward-declared and it's not known if it's callable.

If this was checked at sml::sm instantiation, I think it should work.

99 might be related.

Rijom commented 3 years ago

What prevents you from writing the following?

struct outer {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *idle2 + event<e2> = state<inner>
    );
  }
};
etam commented 3 years ago

It doesn't fix the problem. If in my example I replace every state_inner with sml::state<inner>, but leave the forward declaration of state_inner, the code still won't compile.

Rijom commented 3 years ago

What am I missing? The following works fine for me:

namespace sml = boost::sml;

// hpp

struct e1 {};
struct e2 {};

struct inner;
struct idle{};
struct idle2{};
struct s1{};

// cpp

struct inner {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      *sml::state<idle> + event<e1> = sml::state<s1>
    );
  }
};

struct outer {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *sml::state<idle2> + event<e2> = sml::state<inner>
    );
  }
};

int main() {
  sml::sm<outer, sml::testing> sm;
  assert(sm.is(sml::state<idle2>));
  sm.set_current_states(sml::state<inner>);
  sm.set_current_states<decltype(sml::state<inner>)>(sml::state<s1>);
  assert(sm.is(sml::state<inner>));
  assert(sm.is<decltype(sml::state<inner>)>(sml::state<s1>));

  return 0;
}