Partage
  • Partager sur Facebook
  • Partager sur Twitter

Parmètres pour fonction à parmètres inconnus

    18 juillet 2019 à 12:03:50

    Bonjour.

    Je veux passer à un objet un pointeur générique sur une fonction, et une liste de paramètres pour cette fonction, de sorte à ce qu'il puisse appeler n'importe quelle fonction. Je crois avoir déjà vus que ça se faisait dans je ne sais plus quelle bibliothèque, mais je ne sais pas trop comment m'y prendre. Quelle est la meilleur méthode pour écrire un tel objet?

    • Partager sur Facebook
    • Partager sur Twitter
    Le basheur
      18 juillet 2019 à 13:59:03

      Salut,

      A priori, tu as quand même loupé quelques notions car, a priori, si on peut effectivement expliquer au compilateur que l'on veut représenter une fonction dont on ignore le nombre (et, pourquoi pas le type) des paramètres dont elle a besoin -- regarde du coté de std::function pour cela ;) -- il n'en demeure pas moins que la fonction transmise devra quand même bien, à un moment donné, pouvoir être appelée.

      Or, une fonction ne pourra être appelée que ... si on lui transmet le nombre adéquat de paramètre dont les types correspondent aux type des paramètre qu'elle s'attend à recevoir :-°:'(.

      Pour que "la magie opère", il faudra donc bien, à un moment donné, décider du nombre de paramètre et de leur type que la fonction destinée à être appelée devra accepter.

      Il est vrai que les possibilité sont nombreuses, car on peut très bien décider de transmettre une des fonctions parmis

      int add(int i, int j){
          std::cout<<"add called with values "<<i<<" and "<<j<<"\n";
          return i+j;
      }
      int substract(int i, int j){
          std::cout<<"substract called with values "<<i<<" and "<<j<<"\n";
          return i-j;
      }
      int multiply(int i, int j){
          std::cout<<"multiply called with values "<<i<<" and "<<j<<"\n";
          return i*j;
      }
      int divide(int i, int j){
          assert(j!=0 && "nul dividend detected");
          std::cout<<"divide called with values "<<i<<" and "<<j<<"\n";
          return i/j;
      }

      à la fonction

      int execute(std::function<int(int,int)> fun, int i, int j){
          fun(i,j);
      }

      sous une forme proche de

      int main(){
          auto addResult = execute(&add, 3,4);
          auto substractResult = execute(&substract, 10,3);
          auto multiplyResult = execute(&mulitply, 15,2);
          auto divideResult = execute(&divide, 55,5);
          
      }

      Mais on peut aussi expliquer au compilateur que l'on ne sait pas encore trop bien quel type de donnée sera utilisé, même si on sait comment les données sont utilisées sous une forme proche de

      template <typename T>
      T add(T i, T j){
          std::cout<<"add called with values "<< i<<" and "<<j<<"\n";
          return i+j;
      }
      template <typename T>
      T substract(T i, T j){
          std::cout<<"substract called with values "<<i<<" and "<<j<<"\n";
          return i-j;
      }
      
      template <typename T>
      T multiply(T i, T j){
          std::cout<<"multiply called with values "<<i<<" and "<<j<<"\n";
          return i*j;
      }
      template <typename T>
      T divide(T i, T j){
          assert(j!=0 && "nul dividend detected");
          std::cout<<"divide called with values "<<i<<" and "<<j<<"\n";
          return i/j;
      }
      
      template <typename T>
      T execute(std::function<T(T,T)> fun, T i, T j){
          return fun(i,j);
      }
      int main(){
          auto addResult = execute(add<int>, 3,4);  // utilise le type int
          auto substractResult = execute(substract<float>, 10.15f,3.1415f); // utilise le type float
          auto multiplyResult = execute(multiply<unsigned>, 15u,2u); // utilise le type unsigned int
          auto divideResult = execute(divide<double>, 55.15d,5.03d); // utilise le type double
          
      }

      Et on peut même envisager une certaine adaptation du prototype de la fonction qui devra être appelée aux informations dont dispose la fonction appelante au travers des expressions lambda et de std::bind.

      Si bien que l'on dispose d'un panel de possibilité quasi illimité, une fois que l'on sait ce que l'on veut faire ;)  Mais il n'empêche que la première question à laquelle tu devras toujours apportere une réponse, pour pouvoir faire quelque chose de correct dans ce genre de situation est et restera

      Quel est le prototype des fonctions que je veux transmettre afin de pouvoir les appeler?

      BelhouariSéhane a écrit:

      Quelle est la meilleur méthode pour écrire un tel objet?

      J'ai l'impression, à la manière dont tu exprimes ta demande, que tu souffre du "syndrome java", dans le sens où tu sembles considérer la programmation orientée objets comme la véritable panacée, comme un outil qui permet tout aussi bien d'enfoncer un clou dans un mur que de faire le café.

      Je te rassure tout de suite, ce syndrome est très courant chez les débutants en C++, et ce n'est donc pas un reproche: c'est une constatation ;)

      Cependant, je me sens obliger de te contredire, car la programmation orientée objet n'est jamais qu'un outil qui est -- comme tous les outils -- admirablement adapté lorsqu'il est utilisé correctement et dans les bonnes conditions et totalement inadapté lorsqu'il est utilisé dans "de mauvaises conditions".


      Si je parle du "syndrome java", c'est parce que l'on n'a pas le choix du paradigme dans ce langage: nous devons utiliser le paradigme orienté objet, à l'exclusion de tout autre.

      Tu ne tardera pas à te rendre compte que C++offre beaucoup plus de liberté à ce niveau là, car il permet d'utiliser et de mélanger trois paradigmes, à savoir:

      • le paradigme "purement procédural" (les fonctions libres)
      • le paradigme orienté objet et
      • le paradigme générique (les fonctions template)

      C++ nous permet donc de n'avoir recours à l'orienté objet que ... si l'on a un réel avantage à l'utiliser.  Or, il y a énormément de situations dans lesquelles une fonction libre, template ou non, apportera une bien meilleure soluton à un problème donné que ne pourrait le faire l'alternative orientée objet (dés que l'orienté objet nous inciterait à créer des fonctions statiques, par exemple).

      Le comité de normalisation en d'ailleurs arrivé à un point tel qu'il commence à rendre un grand nombre de fonctions membres de classes -- créées selon l'approche orientée objet -- accessibles sous forme de fonctions libres à l'intérieur même de la bibliothèque standard (voir, pour l'exemple, std::beginstd::end ou encore std::size).

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        18 juillet 2019 à 16:28:22

        Merci pour la réponse qui m'as beaucoup instruit, mais je crois que j'ai mal été compris.

        A vrai dire, j'utilise direct 2D, et je veux programmer un bouton qui appelle une fonction lorsqu'il est cliqué (je l'ai gardé secret pour ne pas me faire taper sur les doigts, personne n'aime directX sur ce forum).

        En faite, je veux que le bouton marche de manière générale, donc que je puisse après avoir inclus un header "Bouton.h" créer un bouton et lui passer n'importe quelle fonction et liste de paramètres pour qu'il puisse l'appeler lorsqu'il est cliqué. Ainsi, je veux faire une méthode:

        void Bouton::definir_fonction(fonction_A_appeler_si_cliqué,...paramètres_à_passer_à_cette_fonction...);
        

        C'est là que je bloque.  Il faut que n'importe quelle fonction retournant void puisse être entrée dans le bouton quelque soit ses paramètres. Je ne vois pas comment faire ça avec des std::function et des Template.

        • Partager sur Facebook
        • Partager sur Twitter
        Le basheur
          18 juillet 2019 à 16:44:29

          BelhouariSéhane a écrit:

          A vrai dire, j'utilise direct 2D, et je veux programmer un bouton qui appelle une fonction lorsqu'il est cliqué (je l'ai gardé secret pour ne pas me faire taper sur les doigts, personne n'aime directX sur ce forum).

          Pourtant c'est bien DirectX. Par contre, Direct2D, c'est dans les vieilles version de DirectX non ?

          Si j'étais toi, je ferais une classe abstraite :

          class ClicSurBouton()
          {
          
          public:
            virtual Fonction()=0;
          }

          Et chaque action spécifique que tu fais hérite de ClicSurBouton, ainsi de manière générique tu peux appeler ClicSurBouton, et la fonction spécifique que tu donnes, en réalité, c'est une classe qui hérite que tu crées pour l'occasion.

          • Partager sur Facebook
          • Partager sur Twitter

          Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

            18 juillet 2019 à 19:19:03

            Je crois qu'il essaie d'implementer la partie slot de Qt.
            • Partager sur Facebook
            • Partager sur Twitter

            Eug

              19 juillet 2019 à 14:06:07

              BelhouariSéhane a écrit:

              En faite, je veux que le bouton marche de manière générale, donc que je puisse après avoir inclus un header "Bouton.h" créer un bouton et lui passer n'importe quelle fonction et liste de paramètres pour qu'il puisse l'appeler lorsqu'il est cliqué. Ainsi, je veux faire une méthode:

              void Bouton::definir_fonction(fonction_A_appeler_si_cliqué,...paramètres_à_passer_à_cette_fonction...);
              

              Attention, il faut respecter le SRP, et là, tu es bien parti pour ne pas le faire!!!

              Le SRP (Single Responsability Principle ou principe de la responsabilité unique) nous dit que chaque variable, chaque classe, chaque fonction ne doit s'occuper que d'une seule chose pour pouvoir s'en occuper correctement.

              Le rôle d'un bouton, par exemple, c'est d'indiquer lorsque l'utilisateur clique dessus.  La réaction de l'application face à ce clique n'est pas du ressors du bouton, mais "d'autre chose"; de manière à pouvoir mettre n'importe quelle logique (y compris les plus complexes) en oeuvre.

              L'idéal pour arriver à ce résultat, c'est de mettre en place un système de signaux et de slots.  Pour faire simple, et te raccrocher à quelque chose que tu connais peut-être, nous pourrions presque dire que c'est de mettre le patron de conception "observateur" en oeuvre.

              Nous pourrions presque comparer ce système à un système de station radio et d'auditeur: la station radio (le bouton) va fonctionner même lorsque personne ne l'écoute: ce n'est pas parce que l'auditeur éteint sa radio que la station radio cesse d'émettre, nous sommes bien d'accord?.

              Dans l'autre sens, si un auditeur est intéressé par ce qui se dit sur une station radio particulière, il devra aller se connecter sur la fréquence correspondant à la station qui l'intéresse pour pouvoir l'écouter.

              Cette manière de faire sera beaucoup plus souple, parce que grâce à elle, tu pourras réduire la notion de bouton "à sa plus simple expression".  Je m'explique:

              Dans cette configuration, un bouton se réduit à "une surface donnée, qui affiche (ou non) un texte ou une image capable d'émettre un signal lorsque l'utilisateur clique sur la souris et que la souris "pointe" sur la surface donnée.

              Autrement dit, tous les boutons sont identiques, en dehors de quelques caractéristiques spécifiques, telles que:

              • le texte affiché
              • la superficie qu'il recouvre
              • l'image affichée ou
              • la position de son coin supérieur gauche.
              Avec l'énorme avantage qu'une seule sorte de bouton pourra servir à "n'importe quel usage", vue que les données qui permettront de faire appel à la fonction qui doit être exécutées se trouveront "ailleurs" (au niveau de l'élément qui doit réagir au clique), et que toute la logique pourra dés lors être "mise en branle" par l'appel d'une simple fonction ne prenant aucun paramètre disponbile sur l'élément devant réagir

              Tel que tu es parti, tu en arriverais à une situation telle que tous les boutons seraient forcément différents, car ils devraient tous, en plus, disposer des informations nécessaires pour permettre ..; d'appeler la fonction qui doit être exécutée.

              C'est à dire que, si tu veux qu'un bouton puisse appeler une fonciton qui ne prend qu'un entier comme paramètre, ce bouton devra disposer d'une donnée sous forme de int.  Et, juste après, tu vas vouloir créer un fonction capable d'appeler une fonction qui prend un float et une position comme paramètre, et qui devra donc disposer d'une autre sorte de bouton, disposant d'un float et d'une position pour pouvoir appeler la fonction en question.

              Bref, si tu envisage de faire appel à des fonctions qui présentent dix prototypes différents (par rapprot au nombre ou au type des paramètres dont elles ont besoin pour fonctionner), tu vas te retrouver à devoir créer ... dix sortes de boutons différents qui ne pourront jamais servir qu'à ... mettre une logique bien particulière en oeuvre.

              Il est possible d'avoir un système de signaux et de slots qui tient sur 200 lignes de codes (plus les commentaires de génération automatique de code) , ainsi que j'en ai la preuve ==>ici<==.

              L'idée, c'est que, grâce à ce système, le code d'un bouton pourrait être aussi simple que

              class Button{
                 using sig_t = Tools::Signal<>;
              public:
                  using slot_t = typename sig_t::slot_type
                  Position coin_sup_gauche;
                  int hauteur;
                  int largeur;
                  std::string text
                  void draw();
                  Tools::Connection connect(slot_t slot){
                      return sig.connect(slot);
                  }
                  void onEventClique(ClickEvent * event){
                      /* ... y aura peut être quelque chose à faire avant */
                      sig();
                  }
              private:
                 sig_t sig;
              };

              Et, à partir de là, tu pourras créer autant "d'auditeurs" différents que tu veux sous une forme prochede

              /* un récepteur voulant appeler une fonction qui prend
               * un int comme paramètre
               */
              class Truc{
              public:
                  using callbackk_t= std::function<void(int)>;
                  Truc(callback_t const &call):call_{call}{
                  }
                  void connect(Button & button){
                      conn_= button.connect([&](){this->onClique();};
                  }
                  void onClique(){
                     /* tout ce qui pourrait devoir être fait avant */
                     call_(x);
                  }
              private:
                  Tools::Connection conn_;
                  callback_t call_;
                  int x;
              };
              /* un autre qui appelle une fonciton prenant deux
               * float comme paramètres
               */
              class Brol{
              public:
                  using callbackk_t= std::function<void(float, float)>;
                  Truc(callback_t const &call):call_{call}{
                  }
                  void connect(Button & button){
                      conn_= button.connect([&](){this->onClique();};
                  }
                  void onClique(){
                     /* tout ce qui pourrait devoir être fait avant */
                     call_(x, y);
                  }
              private:
                  Tools::Connection conn_;
                  callback_t call_;
                  float x;
                  float y;
              };

              dont l'utilisation serait proche de

              /* la fonciton appelée par Truc */
              void trucFunction(int x){
                  /* ... */
              }
              /* la fonction appelée par Brol */
              void brolFunction(float x, float y){
                  /*... */
              }
              int main(){
                  /* le bouton qui fait réagir Truc */
                  Button buttonTruc;
                  /* et notre donnée truc, bien sur */
                  Truc t(trucFunction);
                  t.connect(buttonTruc);
                  /* le bouton qui fait réagir Brol */
                  Button buttonBrol;
                  /* et notre donnée Brol, bien sur */
                  Brol b(brolFunction);
                  b.connect(buttonBrol);
                  /* ... tout le reste pour utiliser les deux boutons */
              }

              Bien sur, je n'ai mis que les choses absolument essentielles à la compréhension.  J"espère que tu n'arriverais sans doute pas à faire quelque chose d'aussi simple avec l'approche que tu voulais prendre ;)

              • Partager sur Facebook
              • Partager sur Twitter
              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                20 juillet 2019 à 12:48:19

                Je dois avouer que la méthode avec des slot me fait peur. Je vais pour l'instant faire un tableau de bool indiquant l'état des boutons, ce qui respecterais le SRP. Après, l'habile méthode à partir d'héritage est aussi assez tentante.

                Sinon, question subsidiaire: Si qqn s'y connais en directX, je n'arrive pas à centrer le texte dans le bouton, car je ne trouve pas sa longueur, et je ne trouve pas comment faire dans la doc. Si il y aurait un tutoriel à jours qui traite de ce sujet, je suis prenant. 

                Merci pour vos réponses.

                -
                Edité par BelhouariSéhane 20 juillet 2019 à 12:48:44

                • Partager sur Facebook
                • Partager sur Twitter
                Le basheur
                  20 juillet 2019 à 15:29:14

                  BelhouariSéhane a écrit:

                  Je dois avouer que la méthode avec des slot me fait peur.

                  Pourquoi? Un signal n'est jamais qu'une fonction qui est appelée à un moment bien particulier ( lorsque le bouton subit le clique, dans le cas qui nous occupe), dont la logique va parcourir une liste de fonctions afin de les appeler les unes après les autres.

                  La mise en oeuvre est assez surprenante, parce que le code utilise le paradigme générique, mais l'utilisation en est particulièrement simple : une fois que tu as décidé quels paramètres devront être transmis aux fonctions appelées (aucun, dans le cas qui nous occupe), il n'y a plus qu'à "remplir la liste" des fonctions qui doivent être appelées (au travers de la fonction connect) ;). la connexion, renvoyée par cette fonction permet de déconnecter les fonctions qui, pour une raison ou une autre, ne doivent plus être appelées.

                  BelhouariSéhane a écrit:

                  Je vais pour l'instant faire un tableau de bool indiquant l'état des boutons, ce qui respecterais le SRP.

                  Cela te rendrait la tâche beaucoup plus compliquée, car cela impliquerait:

                  • que tous tes boutons devraient être en mesure d'accéder à ton tableau de booléen (ce qui n'est déjà pas la meilleure des choses à faire) et
                  • que tu devrais parcourir l'ensemble de ce tableau pour pouvoir déterminer quelle fonction doit être appelée parce que le bouton correspondant a subit un clique.

                  BelhouariSéhane a écrit:

                  Après, l'habile méthode à partir d'héritage est aussi assez tentante.

                   Si tu pense à la mise en oeuvre d'un patron de conception observer ou autre, tu as bien tors, car ce n'est pas (et de loin) plus facile à mettre en place ;)



                  • Partager sur Facebook
                  • Partager sur Twitter
                  Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                    22 juillet 2019 à 10:11:57

                    koala01 a écrit:

                    Si je parle du "syndrome java", c'est parce que l'on n'a pas le choix du paradigme dans ce langage: nous devons utiliser le paradigme orienté objet, à l'exclusion de tout autre.

                    Tu ne tardera pas à te rendre compte que C++offre beaucoup plus de liberté à ce niveau là, car il permet d'utiliser et de mélanger trois paradigmes, à savoir:

                    • le paradigme "purement procédural" (les fonctions libres)
                    • le paradigme orienté objet et
                    • le paradigme générique (les fonctions template)
                    • Fonctions libres => en java fonctions statiques d'une classe utilitaire (qui sert de namespace)
                    • généricité => depuis java 5 (ce qui n'existe pas - mais faut-il le déplorer ? - c'est les spécialisations)
                    • paradigme fonctionnel =>  lambdas depuis java 8.

                    En fait les langages ne sont pas destinés aux même usages, les comparaisons sont assez futiles.  Par exemple la STL de C++ est certainement très efficace (en temps d'exécution), mais assez biscornue (donc cout de développement plus élevé), quand on voit les simagrées qu'il faut faire pour enlever certains éléments  d'une collection

                       std::vector<int> nombres;
                       ...
                       v.erase(std::remove_if(nombres.begin(), nombres.end(),
                                               [](int n){return n % 3 == 0;}
                                              ),
                                   nombres.end());


                    Bah oui, en C++, remove_if, ça remove pas, ça déplace.  Le paradigme des itérateurs, c'est sympa au début, mais bon, est-ce si commode ?

                    Alors qu'en java

                     ArrayList<Integer> nombres = new ArrayList<Integer>(); 
                     ...
                     // enlever les nombres divisibles par 3 
                     nombres.removeIf(n -> (n % 3 == 0)); 

                     Parce qu'au lieu de se baser sur les itérateurs, on a une hiérarchie de conteneurs, donc polymorphisme au niveau des opérations applicables.

                    -
                    Edité par michelbillaud 22 juillet 2019 à 10:18:51

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Parmètres pour fonction à parmètres inconnus

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown