`c_str()` overload for sub state machines
Currently it is possible to add a custom c_str() function to own state classes. However this does not work for sub state machines. Even with a custom c_str() function get_type_name is called and the result tends to be rather cryptic, because of all the nested internal namespaces.
It would be nice if one could add a c_str() function to sub state machines as it is possible with user defined state types.
i found out that the internal boost::sml::back::policies::get_state_name_t can be used which does the right thing even for nested state machines.
I'd like to visit current states recursively and produce a hierarchy. Could you provide more detail on how to achieve it?
Hi @lpc921. To get the full recursive name i've implemented the following helper functions. I'm not very proud of it and it has to be improved, but i needed something working quickly
template <typename SM>
std::string GetCurrentStateName(const SM& stateMachine)
{
std::string name;
stateMachine.visit_current_states([&name](const auto& state) {
name = boost::sml::back::policies::get_state_name_t<std::decay_t<decltype(state)>>::c_str();
});
return name;
}
template <typename>
struct is_sub_state_machine : std::false_type
{};
template <class T, class... Ts>
struct is_sub_state_machine<boost::sml::back::sm<boost::sml::back::sm_policy<T, Ts...>>>
: std::true_type
{};
template <typename>
struct state_machine_impl : std::false_type
{};
template <class T, class... Ts>
struct state_machine_impl<boost::sml::back::sm<boost::sml::back::sm_policy<T, Ts...>>>
{
using type = T;
};
template <typename SSM, typename SM>
std::string GetCurrentSubStateName(const SM& stateMachine)
{
std::string name;
stateMachine.template visit_current_states<SSM>([&name, &stateMachine](const auto& state) {
name = boost::sml::back::policies::get_state_name_t<std::decay_t<decltype(state)>>::c_str();
using state_repr_t = std::decay_t<decltype(state)>;
using state_t = typename state_repr_t::type;
if constexpr (is_sub_state_machine<state_t>::value)
{
using state_machine_t = typename state_machine_impl<state_t>::type;
name += ".";
name +=
GetCurrentSubStateName<decltype(boost::sml::state<state_machine_t>)>(stateMachine);
}
});
return name;
}
template <typename SM>
std::string GetCurrentFullStateName(const SM& stateMachine)
{
/** @todo find out the size at compile time and reserve accordingly */
std::string name;
stateMachine.visit_current_states([&name, &stateMachine](const auto& state) {
name += GetCurrentStateName(stateMachine);
using state_repr_t = std::decay_t<decltype(state)>;
using state_t = typename state_repr_t::type;
if constexpr (is_sub_state_machine<state_t>::value)
{
using state_machine_t = typename state_machine_impl<state_t>::type;
name += ".";
name +=
GetCurrentSubStateName<decltype(boost::sml::state<state_machine_t>)>(stateMachine);
}
});
return name;
}
To get nice names out of it, all entities must have a c_str() function, so not only states but also your sub state machines. For states you get this automatically if you use the "state"_s notation.
This solution requires c++17 because of the constexpr if however it surely can be rewritten without it.
Hi @mechatheo, thanks a lot for providing the code and the hints. I am not familiar with template programming. Your advice helped a lot.
Currently it is possible to add a custom
c_str()function to own state classes. However this does not work for sub state machines. Even with a customc_str()functionget_type_nameis called and the result tends to be rather cryptic, because of all the nested internal namespaces.It would be nice if one could add a
c_str()function to sub state machines as it is possible with user defined state types.
Hi @mechatheo ,
How exactly do you mean add a custom c_str() function to own state class ?
As far as I could see, the c_str() call within visit_current_states allways results in get_type_name implementation of sml.hpp.
This was my unsuccessful attempt to provide a custom c_str() function,.
class StateIdle {
public:
const char* c_str() {
return "Idle";
}
};
// to be used in the transition table like:
state<StateIdle> + event<e> = state<class AnotherState>
Hi @CrazyDaisyRecords :wave:
sorry it took me so long :see_no_evil:. I believe you're missing the static keyword in your example, so try this instead and let me know if it works for you.
class StateIdle {
public:
static auto c_str() {
return "Idle";
}
};
Hi @mechatheo, Thank you very much. That works for me! Awesome!
@mechatheo I tried to make your code run on C++14 but without any success... It does work though in C++17 as you said! Regardless, I find the source code very hard to read so I came up with a different yet simpler way of getting the current full state name. You can interract with the source code here!
And here is the GitHub Repository I made for experimentation with Boost::SML https://github.com/xorz57/BoostSMLProject
Source Code
#include <boost/sml.hpp>
#include <cstdio>
#include <list>
#include <string>
#define ENTER1(State) (state<State> + sml::on_entry<_> / []() { Logger::Enter(#State); Logger::Print(); })
#define ENTER2() (X + sml::on_entry<_> / []() { Logger::Enter("X"); Logger::Print(); })
#define LEAVE1(State) (state<State> + sml::on_exit<_> / []() { Logger::Leave(); })
#define LEAVE2() (X + sml::on_exit<_> / []() { Logger::Leave(); })
class Logger {
public:
static void Print() {
bool first = true;
for (const std::string &state : sStates) {
if (!first) std::printf("/");
std::printf("%s", state.c_str());
first = false;
}
std::printf("\n");
}
static void Enter(const std::string &state) { sStates.emplace_back(state); }
static void Leave() { sStates.pop_back(); }
private:
static std::list<std::string> sStates;
};
std::list<std::string> Logger::sStates;
struct EventA1 { bool flag = false; };
struct EventA2 { bool flag = false; };
struct EventA3 { bool flag = false; };
struct EventB1 { bool flag = false; };
struct EventB2 { bool flag = false; };
struct EventB3 { bool flag = false; };
namespace sml = boost::sml;
struct StateA {
struct StateA0 {};
struct StateA1 {};
struct StateA2 {};
auto operator()() const noexcept {
using namespace sml;
const auto GuardA1 = [](auto event) { return event.flag; };
const auto GuardA2 = [](auto event) { return event.flag; };
const auto GuardA3 = [](auto event) { return event.flag; };
const auto ActionA1 = [](auto) { std::printf("ActionA1\n"); };
const auto ActionA2 = [](auto) { std::printf("ActionA2\n"); };
const auto ActionA3 = [](auto) { std::printf("ActionA3\n"); };
return make_transition_table(
// clang-format off
*state<StateA0> + event<EventA1> [GuardA1] / ActionA1 = state<StateA1>
,state<StateA1> + event<EventA2> [GuardA2] / ActionA2 = state<StateA2>
,state<StateA2> + event<EventA3> [GuardA3] / ActionA3 = X
,ENTER1(StateA0)
,ENTER1(StateA1)
,ENTER1(StateA2)
,ENTER2()
,LEAVE1(StateA0)
,LEAVE1(StateA1)
,LEAVE1(StateA2)
,LEAVE2()
// clang-format on
);
}
};
struct StateB {
struct StateB0 {};
struct StateB1 {};
struct StateB2 {};
auto operator()() const noexcept {
using namespace sml;
const auto GuardB1 = [](auto event) { return event.flag; };
const auto GuardB2 = [](auto event) { return event.flag; };
const auto GuardB3 = [](auto event) { return event.flag; };
const auto ActionB1 = [](auto) { std::printf("ActionB1\n"); };
const auto ActionB2 = [](auto) { std::printf("ActionB2\n"); };
const auto ActionB3 = [](auto) { std::printf("ActionB3\n"); };
return make_transition_table(
// clang-format off
*state<StateB0> + event<EventB1> [GuardB1] / ActionB1 = state<StateB1>
,state<StateB1> + event<EventB2> [GuardB2] / ActionB2 = state<StateB2>
,state<StateB2> + event<EventB3> [GuardB3] / ActionB3 = X
,ENTER1(StateB0)
,ENTER1(StateB1)
,ENTER1(StateB2)
,ENTER2()
,LEAVE1(StateB0)
,LEAVE1(StateB1)
,LEAVE1(StateB2)
,LEAVE2()
// clang-format on
);
}
};
struct Main {
auto operator()() const noexcept {
using namespace sml;
Logger::Enter("Main");
return make_transition_table(
// clang-format off
*state<StateA> = state<StateB>
,state<StateB> = X
,ENTER1(StateA)
,ENTER1(StateB)
,ENTER2()
,LEAVE1(StateA)
,LEAVE1(StateB)
,LEAVE2()
// clang-format on
);
}
};
int main() {
sml::sm<Main> sm;
sm.process_event(EventA1{true});
sm.process_event(EventA2{true});
sm.process_event(EventA3{true});
sm.process_event(EventB1{true});
sm.process_event(EventB2{true});
sm.process_event(EventB3{true});
}
Output
Main/StateA
Main/StateA/StateA0
ActionA1
Main/StateA/StateA1
ActionA2
Main/StateA/StateA2
ActionA3
Main/StateA/X
Main/StateB
Main/StateB/StateB0
ActionB1
Main/StateB/StateB1
ActionB2
Main/StateB/StateB2
ActionB3
Main/StateB/X
Main/X
Nevermind! Just found this undocumented example.
By undocumented I mean it wasn't documented on the wiki https://boost-ext.github.io/sml/examples.html. I had to look up the repository.