Est-il possible de stocker un pack de paramètres d'une manière ou d'une autre pour une utilisation ultérieure?
template <typename... T>
class Action {
private:
std::function<void(T...)> f;
T... args; // <--- something like this
public:
Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
void act(){
f(args); // <--- such that this will be possible
}
}
Puis plus tard:
void main(){
Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);
//...
add.act();
}
c++
c++11
variadic-templates
Eric B
la source
la source
Réponses:
Pour accomplir ce que vous voulez faire ici, vous devrez stocker vos arguments de modèle dans un tuple:
std::tuple<Ts...> args;
De plus, vous devrez changer un peu de constructeur. En particulier, l'initialisation
args
avec unstd::make_tuple
et autorisant également les références universelles dans votre liste de paramètres:template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {}
De plus, vous devrez configurer un générateur de séquence semblable à celui-ci:
namespace helper { template <int... Is> struct index {}; template <int N, int... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {}; template <int... Is> struct gen_seq<0, Is...> : index<Is...> {}; }
Et vous pouvez implémenter votre méthode en prenant un tel générateur:
template <typename... Args, int... Is> void func(std::tuple<Args...>& tup, helper::index<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, helper::gen_seq<sizeof...(Args)>{}); } void act() { func(args); }
Et c'est ça! Alors maintenant, votre classe devrait ressembler à ceci:
template <typename... Ts> class Action { private: std::function<void (Ts...)> f; std::tuple<Ts...> args; public: template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {} template <typename... Args, int... Is> void func(std::tuple<Args...>& tup, helper::index<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, helper::gen_seq<sizeof...(Args)>{}); } void act() { func(args); } };
Voici votre programme complet sur Coliru.
Mise à jour: Voici une méthode d'assistance par laquelle la spécification des arguments du modèle n'est pas nécessaire:
template <typename F, typename... Args> Action<Args...> make_action(F&& f, Args&&... args) { return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...); } int main() { auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3); add.act(); }
Et encore une fois, voici une autre démo.
la source
void print(const std::string&); std::string hello(); auto act = make_action(print, hello());
n'est pas bon. Je préfère le comportement destd::bind
, qui fait une copie de chaque argument à moins que vous ne le désactiviez avecstd::ref
oustd::cref
.args(std::make_tuple(std::forward<Args>(args)...))
àargs(std::forward<Args>(args)...)
. BTW je l'ai écrit il y a longtemps et je n'utiliserais pas ce code dans le but de lier une fonction à certains arguments. Je voudrais juste utiliserstd::invoke()
ou destd::apply()
nos jours.Vous pouvez utiliser
std::bind(f,args...)
pour cela. Il générera un objet mobile et éventuellement copiable qui stockera une copie de l'objet fonction et de chacun des arguments pour une utilisation ultérieure:#include <iostream> #include <utility> #include <functional> template <typename... T> class Action { public: using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...)); template <typename... ConstrT> Action(std::function<void(T...)> f, ConstrT&&... args) : bind_(f,std::forward<ConstrT>(args)...) { } void act() { bind_(); } private: bind_type bind_; }; int main() { Action<int,int> add([](int x, int y) { std::cout << (x+y) << std::endl; }, 3, 4); add.act(); return 0; }
Notez qu'il
std::bind
s'agit d'une fonction et que vous devez stocker, en tant que membre de données, le résultat de son appel. Le type de données de ce résultat n'est pas facile à prédire (le Standard ne le spécifie même pas avec précision), j'utilise donc une combinaison dedecltype
etstd::declval
pour calculer ce type de données au moment de la compilation. Voir la définition deAction::bind_type
ci - dessus.Notez également comment j'ai utilisé des références universelles dans le constructeur basé sur un modèle. Cela garantit que vous pouvez passer des arguments qui ne correspondent pas
T...
exactement aux paramètres du modèle de classe (par exemple, vous pouvez utiliser des références rvalue à certains desT
et vous les transmettrez tels quels à l'bind
appel.)Remarque finale: si vous souhaitez stocker des arguments en tant que références (afin que la fonction que vous transmettez puisse les modifier, plutôt que simplement les utiliser), vous devez les utiliser
std::ref
pour les envelopper dans des objets de référence. Le simple fait de passer unT &
créera une copie de la valeur, pas une référence.Code opérationnel sur Coliru
la source
add
sont définis dans une portée différente de celle oùact()
est appelée? Le constructeur ne devrait-il pas obtenirConstrT&... args
plutôt queConstrT&&... args
?bind()
? Comme ilbind()
est garanti de faire des copies (ou de se déplacer dans des objets fraîchement créés), je ne pense pas qu'il puisse y avoir de problème.bind_(f, std::forward<ConstrT>(args)...)
comportement n'est pas défini selon la norme, car ce constructeur est défini par l'implémentation.bind_type
est spécifié comme étant une copie et / ou un déplacement constructible, cependant,bind_{std::bind(f, std::forward<ConstrT>(args)...)}
devrait toujours fonctionner.Cette question était de C ++ 11 jours. Mais pour ceux qui le trouvent dans les résultats de recherche maintenant, quelques mises à jour:
Un
std::tuple
membre est toujours le moyen le plus simple de stocker des arguments en général. (Unestd::bind
solution similaire à celle de @ jogojapan fonctionnera également si vous souhaitez simplement appeler une fonction spécifique, mais pas si vous souhaitez accéder aux arguments d'une autre manière, ou passer les arguments à plus d'une fonction, etc.)Dans C ++ 14 et versions ultérieures,
std::make_index_sequence<N>
oustd::index_sequence_for<Pack...>
peut remplacer l'helper::gen_seq<N>
outil vu dans la solution de 0x499602D2 :#include <utility> template <typename... Ts> class Action { // ... template <typename... Args, std::size_t... Is> void func(std::tuple<Args...>& tup, std::index_sequence<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, std::index_sequence_for<Args...>{}); } // ... };
Dans C ++ 17 et versions ultérieures,
std::apply
peut être utilisé pour prendre soin de décompresser le tuple:template <typename... Ts> class Action { // ... void act() { std::apply(f, args); } };
Voici un programme C ++ 17 complet montrant l'implémentation simplifiée. J'ai également mis
make_action
à jour pour éviter les types de référence dans letuple
, ce qui était toujours mauvais pour les arguments rvalue et assez risqué pour les arguments lvalue.la source
Je pense que vous avez un problème XY. Pourquoi se donner la peine de stocker le pack de paramètres alors que vous pourriez simplement utiliser un lambda sur le site d'appel? c'est à dire,
#include <functional> #include <iostream> typedef std::function<void()> Action; void callback(int n, const char* s) { std::cout << s << ": " << n << '\n'; } int main() { Action a{[]{callback(13, "foo");}}; a(); }
la source