91 votes

Comment stocker les arguments de modèles variadiques?

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();
}
 

69voto

0x499602D2 Points 36421

Pour accomplir ce que vous voulez ici, vous aurez à stocker vos arguments de modèle dans un tuple:

std::tuple<Ts...> args;

En outre, vous aurez à changer votre constructeur un peu. En particulier, l'initialisation de args avec un std::make_tuple et permettant également des 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)...)
{}

En outre, vous avez à mettre en place un générateur de séquence comme ceci:

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 mettre en œuvre votre méthode en termes de prise de 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 qu'il! 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'aide par lequel la spécification du modèle arguments ne sont pas nécessaires:

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, c'est une autre démonstration.

23voto

jogojapan Points 26661

Vous pouvez utiliser std::bind(f,args...) pour cette. Il va générer un meuble et, éventuellement, copiable objet qui stocke une copie de la fonction de l'objet et de chacun des arguments pour les utiliser plus tard:

#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 que std::bind est une fonction et vous avez besoin de stocker, en tant que membre de données, le résultat de l'appel à elle. Le type de données de ce résultat n'est pas facile à prédire (le Standard n'a même pas le spécifier précisément), j'ai donc utiliser une combinaison de decltype et std::declval pour calculer ce type de données au moment de la compilation. Voir la définition de l' Action::bind_type - dessus.

Notez également comment j'ai utilisé des références universelles dans l'basés sur des modèles du constructeur. Cela garantit que vous pouvez passer des arguments qui ne correspondent pas à la classe des paramètres de modèle T... exactement (par exemple, vous pouvez utiliser des références rvalue pour certains de l' T et vous aurez transmis tels quels à l' bind appel.)

Dernière remarque: Si vous souhaitez stocker les arguments que les références (de sorte que la fonction que vous passez peut modifier, plutôt que de simplement l'utiliser, eux), vous devez utiliser std::ref pour les envelopper d'objets de référence. Le simple passage d'un T & va créer une copie de la valeur, pas une référence.

Code opérationnel sur Coliru

3voto

Casey Points 18217

Je pense que vous avez un problème XY. Pourquoi se donner tant de peine pour stocker le pack de paramètres alors que vous ne pouvez utiliser qu'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();
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X