0

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.

5
  • "advice on how to improve my code" -- This tends to be subjective and off-topic for Stack Overflow (see the help center). However, if you have a specific detail that you would like to improve, that might be on topic. Commented Sep 19, 2024 at 16:35
  • "I'm currently reading Modern C++ Design by Andrei Alexandrescu" -- If you were asking a question about the book, this could warrant being the first thing in your question. However, since you are working beyond the book, this is just your personal history so should probably be removed from the question. (If it somehow provides useful context, it could be kept, but at the end of your question, not the beginning. Start with your question.) Commented Sep 19, 2024 at 16:37
  • I'm not saying it can't be done (although I'm not exactly sure what the end goal is) but why is it important to supply the template parameters as one parameter to the helper classes? Will you have multiple parameter packs later or what's the motivation? If you pass them separately, re-assembling them in the helper classes is easier than splitting them up again. Commented Sep 19, 2024 at 16:49
  • To directly fix "this is wrong", you can use std::tuple<Args...>. To pass R and Args in a single template parameter, you can use R(Args...). Anyway it's not clear enough for me to write a full answer. Commented Sep 19, 2024 at 16:50
  • This construct is already implemented by modern C++, it is called std::function. Commented Sep 19, 2024 at 16:55

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, Ted. That is exactly what I was looking for.
@TylerD007 Great! You're welcome!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.