Search code examples
c++recursionc++17variadicfold-expression

Fold Expressions, parameter pack expansion, recursion in class member function


I'm trying to create a Finite State Machine hierarchy type structure. What I'm trying to do is check to see if the current state exist if it doesn't return, then check to see if all of the next states exist. As soon as one of them fails it will return as well.

I'm not sure if it can be done using fold expressions or variadic parameter pack expansion or not, but I've been getting errors for parameter pack not being expanded. I'm not sure if I can do it this way or if I would need a helper function or some other mechanism.

Here is my approach:

template<unsigned N>
class FSM {
public:
    std::vector<State<N>> states;

    // ... other parts of class

    template<typename Current, typename... NextStates>
    void addStateTransition(Current* currentState, NextStates*... nextStates) {
        // look to see if the current state is a state in our container.
        auto current = std::find(states.begin(), states.end(), currentState);
        if (current == states_.end()) {
            std::cout << "Could not find " << currentState->id_ << " in this State Machine.";
            return;
        }

        // Able to use fold expressions or not to check if all of the next states are in our container?
        auto next = std::find(states.begin(), states.end(), nextStates); 
        // ? I've tried the ellipsis inside, outside and on both sides of the ending parenthesis, and none of them work. 
        if (next == states.end()) {
            std::cout << "Could not find " << nextStates->id_ << " in this State Machine.";
            return;
        }

        // if all of nextStates... are found, do something else here
    }
};

Solution

  • To use a fold-expression, you need something you can fold over. You need some expression for each element in the parameter pack. The expression you need is complicated: a call to std::find, checking the result, etc. So it's best to stick that in a lambda:

    auto lookup = [&](auto nextState) {
        // one single find
        auto it = std::find(states.begin(), states.end(), nextState); 
        if (it == states.end()) {
            std::cout << "Could not find " << nextState->id_ << " in this State Machine.";
            return false;
        }        
        return true;
    };
    
    // fold over that
    bool const allFound = (lookup(nextStates) && ...);
    

    allFound will be true if all the states are found, or false if at least one is missing... in which case something will be logged. This handles empty packs too... if nextStates... is empty, allFound is trivially true.