Search code examples
c++variadic-templatesvariadic-functionsfunctorvariadic

Call a variadic template functor in a variadic template parameter pack if it satisfies a condition


I have a variadic template functor class:

template <typename Result, typename... Arguments>
class Functor
{
public:
    using FunctionType = std::function<Result(Arguments...)>;
    Result operator() (const Arguments&... arguments) { return Function(arguments); }
    std::string GetName() { return Name; }
    Functor(const std::string& name, const FunctionType& function) : Name(name), Function(function) { }
private:
    std::string Name;
    FunctionType Function;
}

and a variadic template function:

template <typename... Functors>
void Function(const Functors&... functors) { }

Let's say I have declared some Functors and want to use them in Function:

Functor<int, int> f
(
    "f",
    [](int x) -> int { return 2 * x - 1; }
);
Functor<int, int, int> g
(
    "g",
    [](int x, int y) -> int { return x + 2 * y - 3; }
);
Function(f, g);

Inside Function, I want to find out which Functor passed to it in the parameter pack satisfies a certain condition, and if it does, call the functor. Like the following pseudocode:

template <typename... Functors>
void Function(const Functors&... functors)
{
    foreach (Functor functor in functors)
    {
        if (functor.GetName() == "f")
        {
            functor(); // the functor can have different parameter lists so this is another problem
        }
    }
}

I want to know if there's a way to do this. I also want to know that since the Functors can have varying parameter lists, how do I call them even if I were able to find the correct Functor to call? Suppose that there's a std::vector<int> and when the Functor takes three ints as its argument, is it possible to take the first three ints and pass them to the Functor?


Solution

  • In C++17, this is easily solvable using fold expressions.

    #include <functional>
    #include <string>
    
    template <typename Result, typename... Arguments>
    class Functor
    {
    public:
        using FunctionType = std::function<Result(Arguments...)>;
        Result operator() (const Arguments&... args) {
            return this->Function(arguments...);
        }
        std::string GetName() {
            return this->Name;
        }
        Functor(const std::string& name, const FunctionType& function)
            : Name(name), Function(function) { }
    
    private:
        std::string Name;
        FunctionType Function;
    };
    
    template <typename... Functors>
    void ForEachFunctor(const Functors&... functors)
    {
        ((functors.getName() == "f" && functors()), ...);
    }
    

    Here, we exploit the short-circuiting of the && operator. Only if the condition functors.getName() == "f" is true, will the right hand side of the operator be evaluated.

    A slightly less hacky approach uses a separate function:

    template <typename Functor>
    void InvokeIfNamedF(const Functor &functor) {
        if (functor.GetName() == "f")
            functor();
    }
    
    template <typename... Functors>
    void ForEachFunctor(const Functors&... functors)
    {
        (InvokeIfNamedF(functors), ...);
    }
    

    The elements of the parameter pack are combined using the comma-operator. However, in this example, we are invoking each functor with no parameters. If all functors have distinct signatures then passing them as one pack and invoking them conditionally all at once is probably not possible.