Search code examples
c++templatesc++20fsm

Get list of types for which templated member method is specialized


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?


Solution

  • 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> {
    };