web-dev-qa-db-fra.com

Comment stocker des arguments de modèle variadic?

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

Plus tard:

void main(){
    Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);

    //...

    add.act();
}
75
Eric B

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 votre constructeur. 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)...)
{}

De plus, vous devrez configurer un générateur de séquence un peu 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 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 ç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.

57
0x499602D2

Vous pouvez utiliser std::bind(f,args...) pour cela. Il va générer un objet mobile et éventuellement copiable qui stocke 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;
}

Remarquerez que std::bind est une fonction et 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évoir (le Standard ne le spécifie même pas précisément), donc j'utilise une combinaison de decltype et std::declval pour calculer ce type de données au moment de la compilation. Voir la définition de Action::bind_type au dessus de.

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

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 de simplement les utiliser), vous devez utiliser std::ref pour les envelopper dans des objets de référence. Juste passer un T & créera une copie de la valeur, pas une référence.

Code opérationnel sur Colir

20
jogojapan

Je pense que vous avez un problème XY. Pourquoi se donner la peine de stocker le pack de paramètres alors que vous pouvez 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();
}
3
Casey