IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Foncteur, variadic templates et héritage


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre très actif
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Par défaut Foncteur, variadic templates et héritage
    Je viens de decouvrir les variadic templates (sous Gcc 4.5) et j'ai tenté de créer un petit foncteur générique qui conserve l'instance d'objet et la méthode que l'on veut y appliquer

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    template <typename C, typename R, typename... Args>
    class GenFonc {
     
    typedef R (C::*functor)(Args...);
     
    public:
     
      GenFonc(C& obj, functor f):mObj(obj), mf(f){}
     
      R execute(Args... args)
      {
        return (mObj.*mf)(args...);
      }
     
    private :
     
        C& mObj;
        functor mf;
     
    };
    Par contre je n'ai pas trouvé le moyen de conserver Args... d'une manière ou d'une autre, je suis donc obligé d'utiliser le foncteur comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Print{
    
    public:
      void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<<endl; }
      int print2(){ cout<<"print2"<<endl; return 2;}
    
    };
    
    int main(){
    
    Print p = Print();
    
    string s = "toto";
    GenFonc<Print, void, string, string> gf1(p, &Print::print1);
    gf1.execute(s, "test");
    
    GenFonc<Print, int> gf2(p, &Print::print2);
    int n = gf2.execute();
    
    //je voudrais faire ceci :
    string s = "toto";
    GenFonc<Print, void, string, string> gf1(p, &Print::print1, s, "test");
    gf1.execute();
    
    }
    Je voudrais donc différer l'appel

    Autre souci, l'impossibilité d'hériter d'une classe abstraite car obligé de conserver la même signature pour execute(...) et donc de conserver les paramètres templates

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class GenFoncBase
    {
      public :
          virtual ? execute(?) = 0;
    };
    Ce qui du coup m'interdit de placer mon foncteur dans un conteneur STL

    Est il possible de sortir de cette impasse ?

  2. #2
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Il me semble que pour capturer les arguments, il faut d'abord les stocker dans un membre de type std::tuple<Args...> puis réutiliser ce tuple dans execute(). Mais ça devient vite extrêmement casse tête.

    Avant d'aller plus loin, je voudrais faire remarquer que GenFonc est très similaire à std::bind. Il faudrait peut être commencer par là.

    Je ne sais pas si tu est familier avec std::bind alors au cas où, voici un topo :

    std::bind est une fonction générant un foncteur qui bind un objet avec une fonction membre (entre autre), permettant un appel tardif.
    Le foncteur généré par std::bind est capable, comme tu le souhaites, soit d'accepter les arguments lors de l'appel du foncteur, soit de stocker directement les arguments lors de la création du foncteur pour les réutiliser lors de l'appel, comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <string>
    #include <iostream>
    #include <functional>
     
    using namespace std;
    using namespace std::placeholders;
     
    class Print
    {
    public:
      void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<< endl; }
      int print2(){ cout<<"print2"<<endl; return 2;}
     
    };
     
    int main()
    {
       Print p;
     
       string s = "toto";
       // avec placeholders  qui indique que le foncteur généré 
       // attends les arguments lors de l'appel.
       auto b1 = bind(&Print::print1, &p, _1, _2); // placeholders
       b1(s, "test");
     
       // sans placeholder, les arguments sont stockés par copie 
       // dans le foncteur généré par bind 
       //(probablement dans un tuple membre de ce foncteur)
       auto b2 = bind(&Print::print1, &p, s, string("test")); 
       b2(); 
    }
    Le seul petit souci de std::bind c'est que le foncteur généré possède un type qui est laissé au choix du compilateur, j'ai donc été obligé d'utiliser auto dans l'exemple pour déduire ce type automatiquement.
    Dans le cas de visual studio, je peux voir au debugger que le type réel est, attention les yeux:
    "std::tr1::_Bind<void,void,std::tr1::_Bind3<std::tr1::_Callable_pmf<void (__thiscall Print::*const)(std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> >),Print,0>,Print *,std::tr1::_Ph<1>,std::tr1::_Ph<2> > >"
    Clairement, ce n'est pas très portable.

    C'est pour ça qu'on utilise en général std::function + std::bind pour résoudre complètement le problème.

    std::function<R(Args...)> est un wrapper généraliste permettant de wrapper n'importe quelle construction C++ étant "appelable" comme une fonction prenant des arguments Args... et retournant R, c'est à dire qui visuellement peuvent être appelées comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    R r = f(arg1, arg2, ... argn);
    f pouvant être une fonction, un pointeur de fonction ou un foncteur.

    Et justement, std::function<R(Args...)> est entre autres capable de wrapper et stocker certains foncteurs produits par std::bind ! On peut donc faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <string>
    #include <iostream>
    #include <functional>
     
    using namespace std;
    using namespace std::placeholders;
     
    class Print
    {
    public:
      void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<< endl; }
      int print2(){ cout<<"print2"<<endl; return 2;}
     
    };
     
    int main()
    {
       Print p;
     
       string s = "toto";
       function<void(string, string)> gf1(bind(&Print::print1, &p, _1, _2));
       gf1(s, "test");
     
       function<int(void)> gf2(bind(&Print::print2, &p));
       int n = gf2();
     
       s = "toto";
       function<void()> gf3(bind(&Print::print1, &p, s, string("test")));
       gf3();
    }
    L'avantage étant que l'on peut stocker des std::function dans un vector par exemple, vu qu'on connait leur type cette fois.

  3. #3
    Membre très actif
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Par défaut
    J'hésitais à utiliser bind et function n'étant pas ûr que l'on pouvait entrer n'importe quel nombre d'arguments ,et ce, sans sans préciser les arguments de templates
    Là je crois qu'il sera difficile de trouver mieux

    Merci pour tout

  4. #4
    Membre très actif
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Par défaut
    (je réactive ce fil temporairement pour éviter d'en créer un nouveau)

    j'aimerais juste pouvoir stocker dans un même conteneur les deux types de foncteurs, sans placeholders et avec placeholders

    pour le premier cas c'est pas trop dur puique function<void()> accepte toutes les fonctions quelques soient les arguments :

    Command :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    class CommandBase
    {
      public :
        virtual void execute() = 0;
    };
     
    class Command : public CommandBase
    {
      std::function<void()> f_;
      public:
        Command() {}
        Command(std::function<void()> f) : f_(f) {}
     
        void execute() { if(f_) f_(); }
        template <typename Func> void setFunction(Func f) { f_ = f; }
     
    };
    CoimmandManager :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    class CommandManager
    {
      typedef map<string, CommandBase*> FMap;
     
      public :
     
        void add(string name, CommandBase* cmd)
        {
          fmap1.insert(pair<string, CommandBase*>(name, cmd));
        }
     
        void execute(string name)
        {
          FMap::const_iterator it = fmap1.find(name);
          if(it != fmap1.end())
          {
              CommandBase* c = it->second;
              c->execute();
          }
        }
     
      private :
     
        FMap fmap1;
     
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
    class Print{
     
    public:
      void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<<endl; }
      int print2(){ cout<<"print2"<<endl; return 2;}
     
    };
     
    int main()
    {
          Print p = Print()
     
          function<void()> f1(bind(&Print::print1, &p, string("test1"), string("test2")));
     
          function<int(void)> f2(bind(&Print::print2, &p));
     
          CommandManager cmdMgr = CommandManager();
          CommandBase* cmdb1 = new Command(f1);
          cmdMgr.add("print1", cmdb1);
          cmdMgr.execute("print1");
     
          CommandBase* cmdb2 = new Command(f2);
          cmdMgr.add("print2", cmdb2);
          cmdMgr.execute("print2");
     
          return 0;
    }
    maintenant le second cas me pose plus de problèmes; il ne semble pas possible de "caster" une fonction bindée avec placeholders en une fonction<void()>

    alors j'ai tenté ceci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    template<typename...Args>
    class Command2 : public CommandBase
    {
      std::function<void(Args...)> f_;
      public:
        Command2<Args...>() {}
        Command2<Args...>(std::function<void(Args...)> f) : f_(f) {}
     
        virtual void execute(Args... args) { if(f_) f_(args...); }
        template <typename Func> void setFunction(Func f) { f_ = f; }
     
    };
    qui utilisé comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    function<void(string, string)> f3(bind(&Print::print1, &p, placeholders::_1, placeholders::_2));
     
    CommandBase* cmdb3 = new Command2<string, string>(f3);
    me génère bien sûr une erreur :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    error: cannot allocate an object of abstract type ‘Command2<std::basic_string<char>, std::basic_string<char> >’
    ../src/Cpp0x_test.cpp:127:1: note:   because the following virtual functions are pure within ‘Command2<std::basic_string<char>, std::basic_string<char> >’:
    ../src/Cpp0x_test.cpp:122:18: note: 	virtual void CommandBase::execute()
    impossible également de templater execute() puisqu'elle est virtuelle

  5. #5
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Bonjour,

    1/ Tu cherches à faire un pattern command ? Dans ce cas c'est étrange c'est déjà ce que fait std::function, ie tu réalises une encapsulation de std::function que n'apporte aucun service suplémentaire (et probablement moins de service même).

    2/ Pour ton erreur c'est normal, executer() est virtuelle pure dans la classe mère, donc si tu veux instancier une classe fille il faut définir executer() dedans. Le polymorphisme n'est pas directement une solution à ton problème (que je n'ai pas encore bien saisie), f.executer() n'a aucun sens si la classe fille gère un foncteur à arguments, executer n'attend pas d'argument tu ne peux pas lui passer via executer.

    Si tu peux, penches toi sur un implémentation de function (boost ou loki), ou un explication détaillée (MC++D a un chapitre à ce sujet).

  6. #6
    Membre très actif
    Profil pro
    Dev
    Inscrit en
    Mai 2009
    Messages
    257
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Mai 2009
    Messages : 257
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Bonjour,

    1/ Tu cherches à faire un pattern command ? Dans ce cas c'est étrange c'est déjà ce que fait std::function, ie tu réalises une encapsulation de std::function que n'apporte aucun service suplémentaire (et probablement moins de service même).
    le problème c'est que je ne peux pas faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    Print p = Print();
     
    function<void(string, string)> f1(bind(&Print::print1, &p, placeholders::_1, placeholders::_2));
     
    //on cast f1 vers f2 => erreur de compilation
    function<void()> f2 = f1;
     
    //on place f2 dans un conteneur 
    vector<function<void()>> vec;
    vec.push_back(f2);
     
    //on récupère f2
    function<void()> f3 = vec.at(n);
     
    //on execute print1
    f3("test1", "test2");
    il faut donc que je masque function<void(string, string)> avec une encapsulation;

    Et je ne parle même pas du type de retour, par exemple si j'utilise print2

    Si tu peux, penches toi sur un implémentation de function (boost ou loki), ou un explication détaillée (MC++D a un chapitre à ce sujet).
    std::function n'est elle pas une copie conforme de boost::function ?
    qu'apporteraient ces librairies par rapport à la stl?

Discussions similaires

  1. Variadic template et héritage
    Par Mikka.Ribou dans le forum C++
    Réponses: 2
    Dernier message: 02/11/2013, 13h19
  2. Template et héritage
    Par rooger dans le forum Langage
    Réponses: 5
    Dernier message: 22/07/2008, 13h48
  3. Foncteur, classes templates et héritage
    Par Floréal dans le forum C++
    Réponses: 8
    Dernier message: 17/06/2007, 21h56
  4. Template ou héritage
    Par bolhrak dans le forum Langage
    Réponses: 6
    Dernier message: 22/12/2006, 11h22
  5. patron, templates et héritages!
    Par kleenex dans le forum C++
    Réponses: 4
    Dernier message: 05/06/2006, 22h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo