Search code examples
c++templatesvariadic-templates

Generalized functors with variadic template arguments


I'm currently reading Modern C++ Design by Andrei Alexandrescu and, in Chapter 5, the author discusses generalized functors as a alternative to the classic Command pattern. Although I'm completely newbie when it comes to metaprogramming, I could realize the book (or at least the edition I'm reading) is not updated with the modern C++ features, so I decided, as a personal challenge, to implement Alexandrescu's ideas using variadic templates and, to a certain extent, I was sucessful:

template <typename R, typename... Args> class FUNCTORIMPLEMENTATION{

    public:
    virtual R operator()(Args&&... args)=0;     
    virtual ~FUNCTORIMPLEMENTATION(){}
};

template <typename F, typename P, typename R, typename... Args> class FUNCTORHANDLER : public FUNCTORIMPLEMENTATION<R, Args...>{
    
    private:
    F functor;
    public:
    
    FUNCTORHANDLER(const F& functor) : functor(functor){} 
    R operator()(Args&&... args){return functor(std::forward<Args>(args)...);} 
};

template <typename R, typename... Args> class FUNCTOR{

    private:
    FUNCTORIMPLEMENTATION<R, Args...>* implementation;
    public:
    template <typename F> FUNCTOR(const F& functor):implementation(new FUNCTORHANDLER<F, FUNCTOR, R, Args...>(functor)){}
    R operator()(Args&&... args){return (*implementation)(std::forward<Args>(args)...);}        
};

class TESTFUNCTOR
{
 
    public:
    float operator()(int i, double d){
 
        return i+d;
    }
}; 

int main(){
    
    TESTFUNCTOR functor;
    FUNCTOR<float, int, double> command(functor);
    float result=command(1, 1.2);
    return 0;
}

As you can see, the code is pretty ugly. One of the biggest issues lies on the declaration of FUNCTORHANDLER. I was unable to find a way to pass FUNCTOR<R, Args...> as a single template argument to the FUNCTORHANDLER's constructor, so I had to declare R and Args... as separate arguments in the FUNCTORHANDLER's template list. I wish I could have implemented something like this:

template <typename R, typename... Args> class FUNCTORIMPLEMENTATION{

    public:
    virtual R operator()(Args&&... args)=0;     
    virtual ~FUNCTORIMPLEMENTATION(){}
};

template <typename F, typename P> class FUNCTORHANDLER : public FUNCTORIMPLEMENTATION<typename P::RType, typename P::ArgsType...>{//R and Args should not be in the FUNCTORHANDLER's template list
    
    private:
    F functor;
    public:
    
    FUNCTORHANDLER(const F& functor) : functor(functor){} 
    R operator()(Args&&... args){return functor(std::forward<Args>(args)...);} 
};

template <typename R, typename... Args> class FUNCTOR{

    private:
    FUNCTORIMPLEMENTATION<R, Args...>* implementation;
    public:
    template <typename F> FUNCTOR(const F& functor):implementation(new FUNCTORHANDLER<F, FUNCTOR<R, Args...>>(functor)){} //FUNCTOR<R, Args...> must be passed as a single template parameter
    R operator()(Args&&... args){return (*implementation)(std::forward<Args>(args)...);}        

    using RType=R;
    using ArgsType=Args;//this is wrong, I don't know how to type aliase a pack expansion
};

Could you give me some advice on how to improve my code? Thank you.


Solution

  • If the intention is to cramp up the functor's arguments and return value in a single template parameter, you could do that. I'd start by defining three primary class templates:

    #include <memory>
    #include <type_traits>
    #include <utility>
    #include <iostream>
    
    // primary class templates, unimplemented:
    template <class...> class FUNCTORIMPLEMENTATION;
    template <class...> class FUNCTORHANDLER;
    template <class...> class FUNCTOR;
    

    The three specializations splits the passed type up into its parts:

    // specialization for FUNCTORIMPLEMENTATION to match on R(Args...)
    template <class R, class... Args>
    class FUNCTORIMPLEMENTATION<R(Args...)> {
    public:
        virtual R operator()(Args&&... args) = 0;
        virtual ~FUNCTORIMPLEMENTATION() {}
    };
    

    The FUNCTORHANDLER can even use the FUNCTOR primary to validate that it's a type based on FUNCTOR:

    // specialization for FUNCTORHANDLER to match on F and the FUNCTOR<R(Args)...>
    template <class F, class R, class... Args>
    class FUNCTORHANDLER<F, FUNCTOR<R(Args...)>> : 
       public FUNCTORIMPLEMENTATION<R(Args...)>
    {
    private:
        F m_functor;
    
    public:
        template<class Ff>
        FUNCTORHANDLER(Ff&& functor) : m_functor(std::forward<Ff>(functor)) {}
    
        R operator()(Args&&... args) override {
            return m_functor(std::move(args)...); // Args&& is not a forwarding ref
        }
    };
    

    And in the functor, it's the same pattern. I only made the raw owning pointer into a unique_ptr:

    // specialization for FUNCTOR to match on R(Args...)
    template <class R, class... Args>
    class FUNCTOR<R(Args...)> {
    private:
        std::unique_ptr<FUNCTORIMPLEMENTATION<R(Args...)>> implementation;
    
    public:
        template <class F>
        FUNCTOR(F&& functor)
            : implementation(
                  std::make_unique<FUNCTORHANDLER<F, FUNCTOR<R(Args...)>>>
                      (std::forward<F>(functor))) {}
    
        R operator()(Args&&... args) {
            return (*implementation)(std::move(args)...);
        }
    };
    

    So at every stage, you pass the composite type as a single template parameter and you then split it up in the specializations.

    Demo