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.
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.