I wrote a following base code for my FSM implementation:
template <typename... Ts>
struct BaseState {
template <typename T>
static bool canTransition() {
if constexpr((std::is_same_v<T, Ts> || ...)) return true;
return false;
}
};
template <typename... Ts>
struct StateMachine {
std::variant<Ts...> active;
template <typename T>
bool activate() {
const bool canTransition = std::visit([](auto&& arg) { return arg.template canTransition<T>(); }, active);
if (!canTransition) return false;
std::visit([](auto&& arg) { return arg.template onExit<T>(); }, active);
active.template emplace<T>();
return true;
}
};
struct B;
struct A : BaseState<B> {
template <typename T>
void onExit() {}
template <>
void onExit<B>() { std::cout << "A -> B" << std::endl; }
};
struct B : BaseState<> {
template <typename T>
void onExit() {};
};
int main() {
StateMachine<A, B> sm;
sm.activate<B>(); // A -> B
sm.activate<A>(); // Shouldn't switch state
}
onExit
member method allows me to perform some actions based on new state type.
I want to automatically deduce A and B template parameters based on onExit
specializations, so I do not need to manually specify all states that the certain state can transition to.
I was also thinking of moving template <typename T> void onExit() {}
definition to BaseState
class, however, It seems that you can't specialize inherited templated member methods.
As an alternative, I was thinking of returning boolean value from onExit
method, indicating whether the state should be transitioned, like so:
struct BaseState {};
template <typename... Ts>
struct StateMachine {
std::variant<Ts...> active;
template <typename T>
bool activate() {
const bool canTransition = std::visit([](auto&& arg) { return arg.template onExit<T>(); }, active);
if (!canTransition) return false;
active.template emplace<T>();
return true;
}
};
struct B;
struct A : BaseState {
template <typename T>
bool onExit() { return false; }
template <>
bool onExit<B>() { std::cout << "A -> B" << std::endl; return true; }
};
struct B : BaseState {
template <typename T>
bool onExit() { return false; }
};
int main() {
StateMachine<A, B> sm;
sm.activate<B>(); // A -> B
sm.activate<A>(); // Shouldn't switch state
}
however, I don't really like the idea of specifying retval manually.
What are my options?
Rather than deduce the pack Ts...
for your current BaseState
, you can use CRTP to check the existence of Current::onExit<Next>
within it.
template <typename S>
struct BaseState {
template <typename T>
bool transition () {
if constexpr (requires(S self) { self.template onExit<T>(); })
{
static_cast<S *>(this)->template onExit<T>();
return true;
}
return false;
}
};
template <typename... Ts>
struct StateMachine {
std::variant<Ts...> active;
template <typename T>
bool activate() {
const bool transitioned = std::visit([](auto&& arg) { return arg.template transition<T>(); }, active);
if (transitioned) {
active.template emplace<T>();
return true;
}
return false;
}
};
struct B;
struct A : BaseState<A> {
template <typename T>
requires std::same_as<T, B>
void onExit() { std::cout << "A -> B" << std::endl; }
};
struct B : BaseState<B> {
};