Partage
  • Partager sur Facebook
  • Partager sur Twitter

Classe et boucle for

Sujet résolu
Anonyme
    30 mai 2020 à 23:13:15

    Salut,

    J'ai une classe qui possède un vector en son sein (c'est l'élément principale de la classe) et et j'aimerais pourvoir parcourir ce tableau avec la boucle for, j'ai donc rajouté un opérateur [] et une méthode size, cela permet de parcourir comme ceci :

    for( int i = 0; i < ma_class.size() ; i++);

    Mais j'aimerais pouvoir faire aussi avec une boucle for comme ceci :

    for ( auto un_element : ma_class );

    Pour plus de simplicité (les accès via l'opérateur [] est lourd visuellement).

    Le problème est que je ne sais pas comment implémenter cette fonctionnalité dans ma classe, mon IDE et le compilo me retour comme quoi aucune méthode begin / end existe dans ma classe et je ne sais pas comment créer ces méthodes.

    Merci d'avance pour vos réponses ^^

    • Partager sur Facebook
    • Partager sur Twitter
      30 mai 2020 à 23:53:30

      renvoyer en sortie d'une fonction un pointeur vers ce vector pourait etre un bon debut.

      Il est effectif qu'un cast devrais se faire automatiquement mais for n''attend pas juste un vector (potentielement d'autres types) donc redefinir les fonction begin() et end() du vector dans ta classe serait une bonne idee.

      -
      Edité par teri terance 31 mai 2020 à 0:07:15

      • Partager sur Facebook
      • Partager sur Twitter

      Il y a deux méthodes pour écrire des programmes sans erreurs. Mais il y a que la troisième qui marche

        30 mai 2020 à 23:53:59

        En première approche, ajouter dans la classe des méthodes begin() et end() qui retournent le résultat de begin/end appliqué au vecteur. Type "iterateur". Voir doc de vector.
        • Partager sur Facebook
        • Partager sur Twitter
          31 mai 2020 à 1:56:26

          Salut,

          De manière générale, les classes qui permettent l'utilisation d'une boucle basée sur les intervalles ont toutes deux points communs:

          elles peuvent répondre aux fonction libres std::begin et std::end

          elles exposent (elle-même) une fonction membre nommée begin et une fonction membre nommée end (en fait, elles exposent, pour la plupart, souvent deux versions de ces fonctions membres: une version constante et une version non constante ;) )

          Le fait est que les fonctions begin et end renvoient, typiquement, des itérateurs sur les éléments de la collection: begin renvoyant un itérateur sur le premier élément de la collection, end renvoyant un itérateur sur ... ce qui suit le dernier élément de la collection.

          Le plus simple, si tu veux qu'une de tes classes perso puissent agir de la même manière, est sans doute d'utiliser la technique dite du "duck typing": "si ca nage comme un canard, que ca cancane comme un canard et que ca vole comme un canard, c'est que j'ai affaire à un canard") en utilisant en interne une collection de ton choix qui présente les fonctions qui t'intéressent pour stocker tes données et en les exposant comme si elles appartenait à ta classe.

          ce qui nous donnera quelque chose qui pourrait être proche de

          /* je ne sais pas ce que tu veux représenter au travers
           * de ta classe, je présente donc un exemple vraiment
           * "passe partout" 
           */
          class MaClass{
              /* pour n'avoir qu'un seul point à modifier si tu veux
               * changer le type de la colleciton interne (et pour
               * ne pas devoir réécrire trente six fois le type de la
               * collection dans ton code)
               */
              using collection_type = std::vector<Type>;
              /* ( où Type correspond au type des données que ta classe
               * doit maintenir )
               */
          public:
              /* pour disposer de l'itérateur modifiable */
              using iterator = typename colleciton_type::iterator
              /* pour disposer de l'itérateur constant */
              using const_iterator = typename colleciton_type::const_iterator;
              /* les fonctions begin et end en version non constante */
              iterator begin(){
                  return datas_.begin();
              }
              iterator end(){
                  return datas_.end();
              }
              /* les fonctions begin et end en version constante */
              const_iterator begin() const{
                  return datas_.begin();
              }
              const_iterator end() const{
                  return datas_.end();
              }
              /* si la collection choisie accepte l'utilsation de 
               * l'opérateur [], on peut le fournir en verison constante
               * et en version non constante
               */
              Type& operator[] (size_t index){
                  assert(index < datas_.size() && "index out of bound");
                  return datas_[index];
              } 
              Type const & operator[] (size_t index) const{
                  assert(index < datas_.size() && "index out of bound");
                  return datas_[index];
              } 
              /* pour savoir combien d'élément il y a dans la collection
               */
              size_t size() const{
                  return datas_.size();
              }
              /* pour pouvoir ajouter un élément dans la collection */
              void push_back(Type const & elem){
                  datas_.push_back(elem);
              }
              /* pour pouvoir supprimer tous les éléments de la collection */
              void clear(){
                  datas_.clear();
              }
              /* les autres fonctions "qui vont bien" pour la classe
               *
               * ...
               */
          private:
              /* la collection de données utilisée en interne */
              colleciton_type datas_;
          };                        

          (J'ai pris l'exemple de std:vector dans mon code, parce que c'est sans doute le choix le plus courant qui sera fait, mais tu peux utiliser n'importe quelle collection de la bibliothèque standard selon tes besoins ;) )

          Il va de soi que, à partir du moment où tu considère que  ta classe devra agir comme une collection, elle ne devrait plus avoir d'autre responsabilité, au termes du SRP (Single Responsability Principle ou principe de la responsabilité unique). 

          Cependant, je n'ai mis qu'un nombre restreint de fonctions adaptées aux collections, et il est possible que l'une ou l'autre des fonctions exposées par celles-ci aient un intérêt particulier dans ton cas ;)

          -
          Edité par koala01 31 mai 2020 à 1:58:19

          • 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
            31 mai 2020 à 7:55:01

            michelbillaud a écrit:

            En première approche, ajouter dans la classe des méthodes begin() et end() qui retournent le résultat de begin/end appliqué au vecteur. Type "iterateur". Voir doc de vector.


            Voila un petit exemple, et après on verra

            • pourquoi ce n'est pas une si bonne idée
            • comment on peut faire mieux avec une approche fonctionnelle.
            #include <iostream>
            #include <vector>
            
            using namespace std;
            
            class Machin {
                std::vector<std::string> m_strings;
            public:
                Machin() {
                    m_strings.push_back("hello");
                    m_strings.push_back("world");
                }
            
                auto begin() {
                    return m_strings.begin();
                }
                auto end() {
                    return m_strings.end();
                }
            };
            
            int main()
            {
                Machin m;
                for(auto & s : m) {
                    std::cout << s << " ";
                }
                std::cout << std::endl;
                return 0;
            }
            

            j'ai mis "auto" comme type de retour pour begin() et end(), parce que si on tient à tout expliciter, ça s'écrit

             std::vector<std::string>::iterator begin() {
                    return m_strings.begin();
                }

            Vu qu'on retourne un iterateur sur un vector (standard) de strings (standard). Tout de suite, ça refroidit.

            ----

            Important : c'est pas forcément une bonne idée (sur le plan de la conception) de retourner directement des itérateurs sur une donnée membre. En fait, c'est le plus souvent  une source d'emmerdements, donc une mauvaise idée, puisque les emmerdements, ça demande du travail après, et que travailler, c'est mal.

            1. Pour comprendre, regardons une idée encore pire : retourner une référence au vecteur

            class Machin 
            {
              std::vector<std::string> m_strings;
            
              //...
            
              auto & elements() {
                    return m_strings;
              }
            };
            
            
               for(auto & s : m.elements()) {
                    std::cout << s << " ";
               }
                std::cout << std::endl;


            Ca marche, certes, mais rien n'empêche maintenant le "code client" d'agir sauvagement sur le contenu du vecteur,

            m.elements().clear();
            

            et, on est bien d'accord, normalement c'est à la classe qu'il revient de gérer les modifications de son état interne.

            2. Pareil avec les itérateurs : on peut s'en servir pour flinguer le contenu

               *m.begin() = "Stop it";

            Donc, conclusion : le plus souvent, il faut éviter de "publier" un accès direct à une donnée membre d'un objet. C'est contraire à la notion d'encapsulation.

            (mais bon il arrive parfois qu'on utilise des classes C++ pour faire autre chose que de la programmation orientée objets stricto sensu avec tous les bons principes généraux écrits dans les bouquins par des auteurs célèbres. On peut avoir de bonnes raisons de faire du hors piste en C++, c'est une question de contexte).

            3. Bon ok, j'exagère pour les besoins de la démonstration, on peut quand même s'en tirer avec les itérateurs constants cbegin/cend

                auto begin() {
                    return m_strings.cbegin();
                }
                auto end() {
                    return m_strings.cend();
                }

            mais bon, vous la voyez la belle incohérence de begin() qui retourne le résultat de cbegin() ?  Et la boucle for qui ne marchera pas avec cbegin/cend ? On sent que les itérateurs constants ça a été bricolé après coup pour essayer de rafistoler un problème (c'est ce qui arrive aux bonnes idées qui ont du succès : on les utilise avec enthousiasme, et on finit par tomber sur des cas emmerdants, et là on bricole).

            ---------

            Une solution plus moderne : l'approche fonctionnelle.

            Si l'objectif est

            • exécuter un traitement à tous les éléments du vecteur

            on définit une fonction foreach() qui

            • applique une fonction (donnée en paramètre) à tous les éléments du vecteur

            Voila ce qu'il faut ajouter

            #include <functional>
            
            using namespace std;
            
            class Machin {
                std::vector<std::string> m_strings;
            public:
                // ...
            
                void foreach(std::function<void(const std::string &)> fun) {
                    for(const auto & s : m_strings) {
                        fun(s);
                    }
                }
            };
            
            
            int main()
            {
                // ...
            
                m.foreach([](auto s){
                    std::cout << s << " ";}
                );
                std::cout << std::endl;
            }
            

            Avec ça, on fournit un moyen de parcourir facilement (faut s'habituer aux lambdas, mais c'est pas compliqué, depuis 10 ans que ça existe en C++ et 50 ans ailleurs en informatique), avec garantie qu'on ne bousillera pas l'état interne des objets.








            -
            Edité par michelbillaud 31 mai 2020 à 10:41:51

            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              31 mai 2020 à 17:49:49

              ok thx à vous ^^

              j'ai opté pour la solution avec begin / end et le bricolage, car je peux juste lire et jamais écrire dans l'objet (tout l'écriture est faite à la création) donc ça ne dérange pas d'avoir ce bricolage (begin ou cbegin sont identique) qui en plus est bien plaqué par l'auto dans la boucle for

              -
              Edité par Anonyme 31 mai 2020 à 17:52:17

              • Partager sur Facebook
              • Partager sur Twitter
                31 mai 2020 à 19:46:51

                >> renvoyer en sortie d'une fonction un pointeur vers ce vector pourait etre un bon debut.

                T'aimes bien les pointeurs toi, non ? :D

                Mais sinon je viens de me souvenir de cette technique, que j'ai découvert(e ?) grâce à Bisqwit:

                /**
                 * foo hérite des propriétés protégées et publiques 
                 * de std::vector, DONT celles relatives à l'itérations.
                 */
                template<typename contained_t>
                class foo : public std::vector<contained_t> {
                };
                
                int main() {
                  foo<std::string> my_var;
                  my_var.push_back( "Hello" );
                  my_var.push_back( "World" );
                  my_var.push_back( "!" );
                
                  for ( const auto& s: my_var )
                    std::cout << s << ' ';
                  std::cout << std::endl;
                }



                -
                Edité par Daimyo_ 31 mai 2020 à 20:10:18

                • Partager sur Facebook
                • Partager sur Twitter
                  1 juin 2020 à 9:27:00

                  Daimyo_ a écrit:

                  >> renvoyer en sortie d'une fonction un pointeur vers ce vector pourait etre un bon debut.

                  T'aimes bien les pointeurs toi, non ? :D

                  Mais sinon je viens de me souvenir de cette technique, que j'ai découvert(e ?) grâce à Bisqwit:

                  /**
                   * foo hérite des propriétés protégées et publiques 
                   * de std::vector, DONT celles relatives à l'itérations.
                   */
                  template<typename contained_t>
                  class foo : public std::vector<contained_t> {
                  };
                  
                  int main() {
                    foo<std::string> my_var;
                    my_var.push_back( "Hello" );
                    my_var.push_back( "World" );
                    my_var.push_back( "!" );
                  
                    for ( const auto& s: my_var )
                      std::cout << s << ' ';
                    std::cout << std::endl;
                  }



                  -
                  Edité par Daimyo_ il y a environ 12 heures

                  Cette technique est une horrible calamité, car les collection de  la bibliothèque standard NE PEUVENT EN AUCUN CAS SERVIR DE CLASSE MERE DANS UNE RELATION D'HERITAGE PUBLIC!!!

                   En effet, pour que l'héritage (public) puisse se faire, il faut respecter "certaines règles" concernant le destructeur de la classe parent qui doit être

                  • soit virtutel et public pour permettre la destruction de l'instance de la classe dérivée alors qu'elle "passe pour être" du type de la classe de base,
                  • soit non virtuel mais protégé, de manière à permettre à l'instance de la classe dérivée de détruire la partie de ses données qui correspondent à la classe de base tout en interdisant à cette instance d'être détruite si elle "passe pour être" du type de la classe de base.

                  Les destructeurs des différentes collection de la bibliothèque standard étant non virtuels et public, l'utilisation de ces collection en tant que classe de base dans un héritage risque de mener à des catastrophes. 

                  En outre, aucun des fonctions exposée par les différentes collections de la bibliothèque standard n'est virtuelle, ce qui fait qu'il est impossible d'en redéfinir le comportement au niveau des classes dérivée.

                  Voici un exemple simple pour t'en convaincre du genre de catastrophes encourrues:

                  /* soit un classe dérivée proche de
                   */
                  class Character : public std::vector<int>{
                  public:
                      Character(WeaponType ta, std::string const &name): m_name{name}{
                          switch(ta){
                              case sword:
                                 m_weapon = new TrainingSword;
                                 break;
                              case axe:
                                 m_weapon =  new TrainingAxe;
                                 break;
                              case hammer;
                                 m_weapon = new BattleHammer;
                                 break;
                               default:
                                 assert(false && "You should never come here");
                          }
                      }
                      ~Character(){
                          /* il ne faut pas oublier de détruire l'arme
                           * quand le personnage est détruit
                           */
                          delete m_weapon;
                      }
                  private:
                      std::string m_name;
                      Weapon * m_weapon{nullptr};
                  };
                  
                  /* qui sera utilisée sous une forme proche de
                   */
                  int main(){
                      std::vector<int> * obj = new Character; //CRACK
                      /* Arrivé à ce point, notre instance de Character
                       * passe pour être une instance de std::vector<int>,
                       *
                       * C'est donc le destructeur de std::vector<int> qui
                       * sera appelé lors du
                       */
                       delete obj;
                       /* à ceci près que le destructeur de std::vector n'étant
                        * pas virtuel, le destructeur de Character ne sera
                        * pas appelé, et, par conséquent, l'instance d'arme
                        * pointée par m_weapon ne sera pas détruite
                        * --> fuite mémoire
                        */
                  }

                  Soyons clairs: il n'y a aucune raison conceptuelle pour qu'une classe Personnage (Character, dans mon code) n'hérite de std::vector, nous sommes bien d'accord.

                  Disons juste que c'est le premier exemple qui me soit venu à l'esprit quand j'ai chercher quelque chose qui aurait pu justifier l'utilisation d'un pointeur comme donnée membre ;)

                  Cependant, au delà de l'incohérence purement conceptuelle, cet exemple démontre bien le problème auquel tu sera confronté ;)

                  Dans le pire des cas, lla seule manière plausible d'utiliser une collection de la bibliothèque standard dans une relation d'héritage est d'envisager une relation "est implémentée en termes de", c'est à dire, d'utiliser un héritage privé, sous une forme proche de

                  /* remarque que l'héritage est PRIVE dans le cas présent
                   */
                  class MaClasse : private std::vector<int>{
                  public:
                     /* et, pour permettre l'utilisation de quelques fonctions
                      * "qui vont bien" issues de std::vector, on les
                      * "pousse" dans la partie publique:
                      */
                      using std::vector<int>::cbegin;
                      using std::vector<int>::cend;
                      using sttd::vector<int>::size;
                  };

                  Cependant, cette technique n'a aucun rapport avec l'héritage public à fin de polymorphisme auquel nous pensons généralement lorsque l'on parle d'héritage et se rapproche en réalité beaucoup plus d'une simple composition que de l'héritage.

                  Note d'ailleurs que l'on préférera généralement la composition à cette possibilité, parce qu'elle permet plus de souplesse avec les fonctions que l'on va décider d'exposer, tout en permettant, le cas échéant, de préciser des actions à effectuer avant après l'exécution du comportement spécifique à la fonction envisagée ;)

                  • 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
                    1 juin 2020 à 10:11:19

                    On lit souvent que "les quatre piliers" de la programmation orientée-objets sont

                    • l'encapsulation (un objet est représenté par un état interne et des opérations qui agissent dessus)
                    • l'abstraction (on n'utilise un objet qu'à travers une API -interface- clairement délimitéé)
                    • le polymorphisme (une même variable peut désigner des objets de type differents si ils qui implémentent l'interface qu'on utilise)
                    • l'héritage

                    En fait, c'est une vision falsifiée de l'histoire. L'héritage ne fait pas partie des principes qui ont historiquement conduit à la notion de programmation orientée objet (lire Alan Kay, sur la conception de Smalltalk). L'héritage c'est une façon de construire un type de données en ajoutant des trucs à un type existant.  On pourrait très bien avoir - et ça serait commode - de l'héritage en C : une structure "point3d", c'est une structure "point2d" à qui on ajoute un champ.

                    En gros, l'héritage, c'est faire une extension à partir d'un type de base, et comme a priori l'extension préserve l'interface existante, on prend ça pour du polymorphisme entre le type de base et le type dérivé. Mais bon, c'est douteux. Faut garder l'héritage pour ce qu'il est, baser de fonctionnalités au dessus de code déjà écrit.

                    M'est avis que ce fameux quatrième pilier (il faudrait chercher qui a introduit cette formule marketing) a été rajouté pour faire plaisir aux C++-istes, où le concept d'interface n'est pas matérialisée dans le langage (on utilise des classes abstraites à la place, ce qui n'est pas la même chose).

                    Mais bon, c'est pas comme si Stroustrup lui même n'avait pas prévenu (le langage C++, 2nd ed. 1992) sur l'obstination de certains à persister dans les erreurs triviales de conception :

                    -
                    Edité par michelbillaud 1 juin 2020 à 10:27:36

                    • Partager sur Facebook
                    • Partager sur Twitter
                      1 juin 2020 à 12:50:25

                      @koala01, d'accord ! J'avais juste vu cette technique dans une vidéo de Bisqwit, que j'ai naïvement recopié, sans trop réfléchir par rapport aux genres de problèmes soulevés.
                      • Partager sur Facebook
                      • Partager sur Twitter
                      Anonyme
                        1 juin 2020 à 14:12:30

                        J'ai eu un collègue plus expérimenté que moi, plutôt orienté recherche (ceci explique cela... même s'il y a des limites à tout). J'ai sauté de joie quand il est parti...

                        Une fois il voit mon code et me demande à quoi sert le mot-clé "final". Alors je lui explique et en fait il ne comprenait pas, sincèrement. Ce qui n'a rien d'étonnant : son code se basait uniquement sur l'héritage. Quand je lui disais de le mettre, il me répondait qu'il ne voulait pas utiliser "final" sinon il ne pouvait plus "ajouter de données dans sa classe". Sauf que ça donnait des relations sans queue ni tête !

                        Vu que l'héritage et moi, moins on se voit, mieux on se porte... On a eu du mal à communiquer davantage sur la notion d'architecture.

                        Bon, j'ai compris que de toute façon ça servait à rien de discuter. Le mec te foutait sa struct dans le .cpp entre deux fonctions à la ligne 300... Mais par rapport à quand tu demandes à un autre collègue de ne pas utiliser de tableaux C (sans les libérer !) et qu'en solution, tout ce qu'il fait c'est les déplacer dans le .h, toujours sans les supprimer... Finalement, la struct à la ligne 300 de JaiRienAFaireLa.cpp, c'est pas si mal... ¯\_(ツ)_/¯

                        • Partager sur Facebook
                        • Partager sur Twitter
                          1 juin 2020 à 15:14:39

                          Il est même possible d'aller beaucoup plus loin, vu qu'il est parfaitement possible de créer une structure (voire, de lui donner des fontions membres) directement à l'intérieur d'une fonction, par exemple
                          #include <iostream>
                          #include <stdexcept>
                          int main(){
                              struct Point{
                                  int x;
                                  int y;
                                  /*on pourrait même ajouter des fonctions membres, par exemple */
                                  Point(int posX, int posY):x{posX},y{posY}{
                                      
                                  }
                                  void moveTo(int posX, int posY){
                                      x=posX;
                                      y=posY;
                                  }
                                  std::ostream & toStream(std::ostream & stream) const{
                                      stream<<"x "<<x
                                            <<" y "<<y;
                                      return stream;
                                  }
                              };
                              Point point{4,5};
                              point.toStream(std::cout)<<"\n";
                              point.moveTo(10,15);
                              /* vérifions l'état du flux par la suite */
                              if(! point.toStream(std::cout))
                                  throw std::runtime_error("unable to push point into the stream");
                          }

                          est un code parfaitement légal du point de vue du langage, qui ne fera pousser aucun haut cris au compilateur, et qui donnera exactement le résultat attendu, à savoir

                          a.exe
                          x 4 y 5
                          x 10 y 15

                          Il m'est déjà arrivé d'avoir recours à cette technique dans des cas bien particuliers, dans lesquels je savais que la structure que je fournissais n'aurait pas de sens en dehors de la fonction dans laquelle je la déclarais ;)

                          Bien sur, c'était avant l'arrivée (en 2011) des expressions lambda, généralement pour créer un foncteur dont je n'avais besoin qu'à un endroit bien particulier.  Et pourtant, c'est quand même le genre de choses que j'ai faites ;)

                          Notez d'ailleurs que les expresions lambda nous permettent d'ailleurs de faire des trucs encore plus droles... Voyez le code qui suit

                          #include <iostream>
                          int main(){
                              int x{5};
                              int y{6};
                              auto movePoint = [=](int posX, int posY){        
                                  struct Point{
                                      int x;
                                      int y;
                                      /*on pourrait même ajouter des fonctions membres, par exemple */
                                      Point(int posX, int posY):x{posX},y{posY}{
                                          
                                      }
                                      void moveTo(int posX, int posY){
                                          x=posX;
                                          y=posY;
                                      }
                                      std::ostream & toStream(std::ostream & stream) const{
                                          stream<<"x "<<x
                                                <<" y "<<y;
                                          return stream;
                                      }
                                  };
                                  Point p{x,y};
                                  p.toStream(std::cout)<<"\n";
                                  p.moveTo(posX, posY);
                                  p.toStream(std::cout)<<"\n";
                              };
                              movePoint(10,15);
                          }

                          Me croiriez vous si je vous disais que ce code est tout à fait légal, et qu'il fournit exactement le résultat attendu ???


                          • 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
                            1 juin 2020 à 17:57:17

                            En réalité, ne pas pouvoir déclarer des types ou d'autres fonctions à l'intérieur de fonctions, c'était un gros retard historique de C sur ce qu'on faisait couramment dans les langages de l'époque (Algol 60, Pascal, etc).

                            Dans la mesure où C ne considère pas les fonctions comme des objets du premier ordre (ce qui aurait impliqué des problèmes d'allocation pour gérer des fermetures, comme pour les lambda-expressions) ce n'aurait pourtant pas été compliqué à implémenter (ça a été fait dans les extensions gnu).

                            Et 50 ans plus tard, on s'émerveille de pouvoir faire ce qui n'étonnait personne un demi-siècle plus tôt :-)

                            https://en.wikipedia.org/wiki/Funarg_problem

                            -
                            Edité par michelbillaud 1 juin 2020 à 17:59:02

                            • Partager sur Facebook
                            • Partager sur Twitter
                              1 juin 2020 à 18:35:14

                              Ce n'est pas que je m'émerveille de pouvoir le faire (comme je l'ai dit, j'ai utilisé plus d'une fois cette technique lorsqu'elle s'avérait appropriée).

                              Par contre, je reconnais aussi volontiers que je n'avais jamais tenté l'expérience avec une expression lambda et que, même si je ne voyais aucune raison pour que cela ne fonctionne pas, j'ai été "agréablement surpris" de pouvoir dire que cela fonctionnait très bien :D

                              • 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
                                1 juin 2020 à 21:28:21

                                En C++, on est étonné quand ça marche ? :-)

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  1 juin 2020 à 21:56:23

                                  michelbillaud a écrit:

                                  En C++, on est étonné quand ça marche ? :-)

                                  Pas vraiment, en fait...

                                  Mais les vieux réflexes dus à d'autres langages ont la vie dure :D

                                  EDIT: Et puis, le fait est que nous parlons d'une possibilité offerte par le langage, dont je soupçonnais fortement la présence (pour raisons de cohérence pourrions nous dire), mais sur laquelle je ne m'étais pas encore documenté.

                                  J'ai donc fait l'essai avec une expression lambda en me disant qu'il y avait "de fortes chances" pour que ca marche, mais, en gardant à l'esprit que ca pouvait tout aussi bien, pour ce que j'en savais, être refusé par le compilateur parce que non prévu par la norme.

                                  Le résultat n'était donc qu'une demi surprise, mais elle était agréable quand même ;)

                                  -
                                  Edité par koala01 1 juin 2020 à 22:06:53

                                  • 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

                                  Classe et boucle for

                                  × 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