En me basant sur la réponse de @IllidanS4, j'ai créé une classe modèle qui permet de passer par référence pratiquement n'importe quelle fonction membre avec des arguments prédéfinis et une instance de classe pour un appel ultérieur.
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 / exemple :
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
}
Évidemment, cela ne fonctionnera que si les arguments donnés et la classe du propriétaire sont toujours valides. Pour ce qui est de la lisibilité... veuillez me pardonner.
Edit : Suppression des mallocs inutiles en faisant du tuple un stockage normal. Ajout d'un type hérité pour la référence. Ajout d'une option pour fournir tous les arguments au moment de l'appel. Nous nous efforçons maintenant d'avoir les deux....
Edit 2 : Comme promis, les deux. La seule restriction (que je vois) est que les arguments prédéfinis doivent venir avant les arguments fournis par le runtime dans la fonction de rappel. Merci à @Chipster pour son aide concernant la conformité à gcc. Cela fonctionne avec gcc sur ubuntu et visual studio sur 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 : conformité à la norme clang, plus grande flexibilité et exemples. (Extrait de mon projet de loisir actif, que je prévois de rendre open source... à terme).
//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
}
En écrivant ceci, je suis tombé sur bogue connu de libstdc++ #71096 qui rompt std::get
lorsque >1 argument est donné au moment de la construction du callback. Ce bogue a été marqué comme corrigé dans gcc 11, qui n'a malheureusement pas été intégré dans le repo ubuntu pour le moment (apt dit que je suis à jour avec 9.3.0).