Building on @IllidanS4 's answer, I have created a template class that allows virtually any member function with predefined arguments and class instance to be passed by reference for later calling.
template<class RET, class... RArgs> class Callback_t {
public:
virtual RET call(RArgs&&... rargs) = 0;
//virtual RET call() = 0;
};
template<class T, class RET, class... RArgs> class CallbackCalltimeArgs : public Callback_t<RET, RArgs...> {
public:
T * owner;
RET(T::*x)(RArgs...);
RET call(RArgs&&... rargs) {
return (*owner.*(x))(std::forward<RArgs>(rargs)...);
};
CallbackCalltimeArgs(T* t, RET(T::*x)(RArgs...)) : owner(t), x(x) {}
};
template<class T, class RET, class... Args> class CallbackCreattimeArgs : public Callback_t<RET> {
public:
T* owner;
RET(T::*x)(Args...);
RET call() {
return (*owner.*(x))(std::get<Args&&>(args)...);
};
std::tuple<Args&&...> args;
CallbackCreattimeArgs(T* t, RET(T::*x)(Args...), Args&&... args) : owner(t), x(x),
args(std::tuple<Args&&...>(std::forward<Args>(args)...)) {}
};
Test / example:
class container {
public:
static void printFrom(container* c) { c->print(); };
container(int data) : data(data) {};
~container() {};
void print() { printf("%d\n", data); };
void printTo(FILE* f) { fprintf(f, "%d\n", data); };
void printWith(int arg) { printf("%d:%d\n", data, arg); };
private:
int data;
};
int main() {
container c1(1), c2(20);
CallbackCreattimeArgs<container, void> f1(&c1, &container::print);
Callback_t<void>* fp1 = &f1;
fp1->call();//1
CallbackCreattimeArgs<container, void, FILE*> f2(&c2, &container::printTo, stdout);
Callback_t<void>* fp2 = &f2;
fp2->call();//20
CallbackCalltimeArgs<container, void, int> f3(&c2, &container::printWith);
Callback_t<void, int>* fp3 = &f3;
fp3->call(15);//20:15
}
Obviously, this will only work if the given arguments and owner class are still valid. As far as readability... please forgive me.
Edit: removed unnecessary malloc by making the tuple normal storage. Added inherited type for the reference. Added option to provide all arguments at calltime instead. Now working on having both....
Edit 2: As promised, both. Only restriction (that I see) is that the predefined arguments must come before the runtime supplied arguments in the callback function. Thanks to @Chipster for some help with gcc compliance. This works on gcc on ubuntu and visual studio on windows.
#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif
template<class RET, class... RArgs> class Callback_t {
public:
virtual RET call(RArgs... rargs) = 0;
virtual ~Callback_t() = default;
};
template<class RET, class... RArgs> class CallbackFactory {
private:
template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
private:
T * owner;
RET(T::*x)(CArgs..., RArgs...);
std::tuple<CArgs...> cargs;
RET call(RArgs... rargs) {
return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
};
public:
Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
~Callback() {};
};
public:
template<class U, class... CArgs> static Callback_t<RET, RArgs...>* make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2> CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) : x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>* CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
}
Edit 3: clang compliance, greater flexibility and examples. (Ripped from my active hobby project, which I plan to open source... eventually.)
//CallbackFactory.h
#pragma once
#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif
namespace WITE {
template<class RET, class... RArgs> class Callback_t {
public:
virtual RET call(RArgs... rargs) const = 0;
virtual ~Callback_t() = default;
};
template<class RET, class... RArgs> class CallbackFactory {
private:
template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
private:
RET(T::*x)(CArgs..., RArgs...);
T * owner;
std::tuple<CArgs...> cargs;
public:
Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
~Callback() {};
RET call(RArgs... rargs) const override {
return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
};
};
template<class... CArgs> class StaticCallback : public Callback_t<RET, RArgs...> {
private:
RET(*x)(CArgs..., RArgs...);
std::tuple<CArgs...> cargs;
public:
StaticCallback(RET(*x)(CArgs..., RArgs...), CArgs... pda);
~StaticCallback() {};
RET call(RArgs... rargs) const override {
return (*x)(std::get<CArgs>(cargs)..., rargs...);
};
};
public:
typedef Callback_t<RET, RArgs...>* callback_t;
template<class U, class... CArgs> static callback_t make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
template<class... CArgs> static callback_t make(CArgs... cargs, RET(*func)(CArgs..., RArgs...));//for non-members or static members
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2>
CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) :
x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET2, class... RArgs2> template<class... CArgs2>
CallbackFactory<RET2, RArgs2...>::StaticCallback<CArgs2...>::StaticCallback(RET2(*x)(CArgs2..., RArgs2...), CArgs2... pda) :
x(x), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>*
CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
};
template<class RET, class... RArgs> template<class... CArgs> Callback_t<RET, RArgs...>*
CallbackFactory<RET, RArgs...>::make(CArgs... cargs, RET(*func)(CArgs..., RArgs...)) {
return new wintypename CallbackFactory<RET, RArgs...>::StaticCallback<CArgs...>(func, std::forward<CArgs>(cargs)...);
};
#define typedefCB(name, ...) typedef WITE::CallbackFactory<__VA_ARGS__> name## _F; typedef typename name## _F::callback_t name ;
typedefCB(rawDataSource, int, void*, size_t)
};
//example:
class Integer {
public:
typedefCB(oneInOneOut, int, int);
typedefCB(twoInOneOut, int, int, int);
int value;
Integer(int v) : value(v) {};
int plus(int o) {
return value + o;
};
int plus(int a, int b, int c) {
return value + a + b + c;
};
static int simpleSum(int a, int b) {
return a + b;
};
};
int main(int argc, char** argv) {
Integer::twoInOneOut sumOfTwo = Integer::twoInOneOut_F::make(&Integer::simpleSum);
std::cout << sumOfTwo->call(5, 6) << std::endl;//11
//
Integer seven(7);
Integer::oneInOneOut sevenPlus = Integer::oneInOneOut_F::make<Integer>(&seven, &Integer::plus);
std::cout << sevenPlus->call(12) << std::endl;//19
//
Integer::twoInOneOut seventeenPlus = Integer::twoInOneOut_F::make<Integer, int>(&seven, 10, &Integer::plus);//provide the 1st arg here, and the other 2 when called
std::cout << seventeenPlus->call(52, 48) << std::endl;//117
}
While writing this up, I ran into libstdc++ known bug #71096 which breaks std::get
when >1 argument is given at callback construction time. This bug has been marked fix in gcc 11, which has unfortunately not made it into the ubuntu repo at this time (apt says I'm up to date with 9.3.0)