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

C++ Discussion :

Going Native 2013 - Stephan Lavavej - Don't help the compiler


Sujet :

C++

  1. #1
    Membre confirmé

    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2012
    Messages
    108
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Etudiant
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juillet 2012
    Messages : 108
    Points : 573
    Points
    573
    Par défaut Going Native 2013 - Stephan Lavavej - Don't help the compiler
    Stephan Lavavej - Don't help the compiler
    GoingNative 2013

    La conférence de Stephan. T. Lavavej (alias S.T.L.) lors des GoingNative 2013 est maintenant disponible en rediffusion :


    Voici un résumé de cette conférence :

    Les lambdas ne sont pas toujours mieux

    Utilisez certains foncteurs plutôt que des lambdas.

    Que pensez-vous de ce code ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    sort(v.begin(), v.end(), [] (const Elem& l, const Elem& r) { return l > v; }
    Eh bien, Lavavej explique qu'il est verbeux. En effet, il préconise l'utilisation du foncteur greater dans ce cas là, de la manière suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    sort(v.begin(), v.end(), std::greater<Elem>());
    Les foncteurs greaters, plus, multiplies :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template<class T>
    struct plus {
    T operator()(const T& x, const T& y)
    { return x + y; }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    struct multiplies {
    T operator()(const T& x, const T& y)
    { return x * y; }
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    struct greater {
    bool operator()(const T& x, const T& y)
    { return x > y; }
    };
    À travers ces codes S.T.L. veut nous montrer la simplicité d'utilisation de ces foncteurs par rapport aux lambdas qui sont plus verbeuses. Utiliser certains foncteurs intéressants et utiles (greater, less, plus, multiplies, et d'autres...) permet de gagner grandement en lisibilité.


    Les diamonds

    Au cours de cette conférence, Stephan T. Lavavej a également présenté, un nouveau type de foncteurs, nommés "diamonds" sur lesquels il travaille.

    Il a effectué une proposition pour la standardisation du C++14. Voici un code d'exemple d'utilisation :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    vector<const char *> v { "cute", "fluffy", "kittens" };
    set<string, greater<>> s { "Hungry", "EVIL", "ZOMBIES" };
    vector<string> dest;
    transform(v.begin(), v.end(), s.begin(), back_inserter(dest), plus<>());
    Grâce aux codes suivants, avec les conteneurs vector et set, ainsi que l'algorithme transform, Lavavej explique, et prouve leur simplicité d'utilisation, ainsi que leur côté extrêmement pratique.


    En résumé :
    Utilisez les foncteurs "diamonds" lorsqu'ils seront disponibles, cela améliorera la lisibilité.
    En plus d'être moins verbeux, ils sont plus efficaces et permettent d'éviter les divers dangers de conversion de types.


    L'utilité du const X&&

    Dans cette petite partie, S.T.L. nous explique que :

    • const X&& n'est pas utile pour la sémantique de déplacement ;
    • const X&& n'est pas parfait pour le transfert parfait également ;
      Excepté X&& qui déclenche la déduction d'argument d'un template.


    Cependant, const X&& permet de rejeter des temporaires.

    Synthèse générale :
    • Apprenez à utiliser les références rvalues.

    • Ne surchargez pas les transferts parfaits, car ils sont intentionnellement gourmands.

    • Utilisez les temporaires seulement quand ils sont nécessaires.

    • Utilisez "const X&&" pour rejeter les temporaires

    • Mais surtout essayez d'écrire du code uniquement lorsque vous savez comment celui-ci va se comporter. Cela améliore votre conception et réduit le nombre de modifications apportées à votre code par rapport à un développement sans réflexion.

      C'est généralement vrai, mais spécialement pour les références rvalues, sinon veuillez demander, ou vous faire conseiller par un expert.

    • Utilisez certains foncteurs (greater, plus, multiplies, less, etc...), plutôt que les lambdas qui nuisent à la lisibilité.


    Évènement : GoingNative 2013

    Et vous
    Qu'en pensez-vous ?
    Le paradigme de chacun ne dépend pas de lui, mais de son éducation...

    Le mot donne à la pensée son existence la plus haute et la plus noble.
    Spinoza

    Quiconque n'est pas choqué par la théorie quantique ne la comprend pas.
    Niels Bohr

    http://isocpp.org/

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Ca me rassure, car je me souviens de quelques débats dans lesquelles je prenais des position similaires.

    Je ne nies absolument pas l'intérêt des expressions lambda, mais je crois que, comme toute nouvelle technique, il est important de se l'approprier et de trouver les cas où elle apporte réellement un plus par rapport à l'existant.

    Le gros intérêt des foncteurs, selon moi (quel que soit le nom qu'on puisse leur donner, du moment qu'ils soient explicites ) est qu'il est possible de les réutiliser bien plus qu'une expression lambda et qu'il y a même moyen de les généraliser si l'on est un peu attentif aux différents concepts mis en oeuvre dans un projet
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    J'avoue en effet être forcé de réécrire assez souvent le même genre de lambdas. La plupart du temps, c'est parce que je dois déréférencer un pointeur dans la comparaison. Une façon d'améliorer ça serait utile.

  4. #4
    Membre confirmé

    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2012
    Messages
    108
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Etudiant
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juillet 2012
    Messages : 108
    Points : 573
    Points
    573
    Par défaut
    Citation Envoyé par koala01
    Je ne nies absolument pas l'intérêt des expressions lambda, mais je crois que, comme toute nouvelle technique, il est important de se l'approprier et de trouver les cas où elle apporte réellement un plus par rapport à l'existant.
    Tout à fait d'accord.

    Comme, je disais tout à l'heure a germinolegrand, la vision de lavavej sur les lambdas est je pense une vision que bien du monde devrait avoir.
    A savoir certes elles sont tres pratiques mais dans certains cas, on a des foncteurs qui peuvent faire l'affaire.

    Maintenant le fait est qu'au niveau d'une reutilisation rien ne vaut les foncteurs.

    Cependant je trouve qu'une lambda utilisée avec auto ameliore la lisiblité d'un code.

    C'est d'ailleurs ce que j'essaye de faire en ce moment c'est à dire :
    Favoriser une utilisation des foncteurs, plutot qu'une lambda, dans certains cas comme l' exemple de S.T.L.
    Le paradigme de chacun ne dépend pas de lui, mais de son éducation...

    Le mot donne à la pensée son existence la plus haute et la plus noble.
    Spinoza

    Quiconque n'est pas choqué par la théorie quantique ne la comprend pas.
    Niels Bohr

    http://isocpp.org/

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    J'avoue en effet être forcé de réécrire assez souvent le même genre de lambdas. La plupart du temps, c'est parce que je dois déréférencer un pointeur dans la comparaison. Une façon d'améliorer ça serait utile.
    En fait, on en revient toujours au fait qu'il faut avoir une vue "transversale" de son projet : il y a des "concepts" qui apparaissent, de manière générale, dans presque la totalité de tes hiérarchies de classes.

    Mettons que tu travailles sur des objets pour lesquels tu aies décidé de fournir à chaque fois un identifiant unique.

    Tu auras sans doute travaillé correctement en veillant à garder tes hiérarchies clairement distinctes, mais la plupart d'entre elle auront une propriété commune : une fonction id() (ou getId() ou ce que tu veux) qui renvoie l'identifiant unique de l'objet en question

    Le fait est que cet identifiant unique est un très bon candidat pour les tentatives de classement, et que tu vas donc sans doute régulièrement utiliser une lambda pour vérifier quel identifiant est plus petit que l'autre (dans une même game d'objets, s'entend)

    Dés lors, pourquoi ne pas créer un foncteur spécial sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename T>
    struct lessById{
        bool operator()(T const & first, T const & second) const{
            return first.id() < second.id();
        }
        /* comme cela, on prend les pointeurs en compte également ;) */
        bool operator()(T * first, T * second) const{
            return this->(*first, *second);
        }
    };
    Tu l'écris une fois, tu l'utilises partout où tu en as besoin

    Puis, tu en viens à te rendre compte que pas mal de tes hiérarchies présente le concept de "positionnable" et que tu tries souvent des objets en fonction de la position à laquelle ils se trouvent.

    Hé bien, c'est reparti : on crée un autre foncteur qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template <typename T>
    struct lessByPosition{
        bool operator()(T const & first, T const & second) const{
            return first.position().x() < second.position().x() ||
                     ( first.position.x() == second.position.x() &&
                       first.position.y() < second.position.y() );
        }
        /* comme cela, on prend les pointeurs en compte également ;) */
        bool operator()(T * first, T * second) const{
            return this->(*first, *second);
        }
    };
    ET ainsi de suite pour chaque concept plus ou moins transversal

    Maintenant, il n'y a strictement rien qui ne t'empêche de faire pareil pour un concept qui ne serait utilisé que pour un certain type bien particulier pour lequel les comparaisons sont fréquentes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void lessBySomething{
        bool operator()(SomeType const & first, SomeType const & second) const{
            /* the correct logic to use */
    }
        bool operator()(SomeType  * first, SomeType  * second) const{
            return this->(*first, *second);
        }
    };
    Ce qui importe, c'est qu'au lieu de copier sans cesse tes labmda (qui sont effectivement particulièrement verbeuses), tu te retrouve avec une seule "version" du code, qui se trouve à un endroit bien déterminé dans ton projet, et que, si tu dois en corriger la logique, tu n'auras qu'un seul endroit du code à vérifier.

    Les expressions lambda devraient, au final, être réservées à des situations dont on a la certitude qu'elles ne serviront qu'à cet endroit bien particulier
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Inactif
    Inscrit en
    Août 2013
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Août 2013
    Messages : 27
    Points : 52
    Points
    52
    Par défaut
    Au final, c'est juste un rappel des règles existantes (factoriser son code, utiliser l'existant), rien de nouveau. C'est juste que les gens oublient souvent ces règles de base (@germinolegrand non non, je vise personne... tu en es où avec l'écriture de ta lib de GUI ? )

  7. #7
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Fort heureusement mes lambdas sont toujours assez courtes et simples pour qu'il soit plus rapide/facile de les écrire sans aller voir ailleurs ^^ (vite écrit quand on a le coup de main ).

    Ce sont les foncteurs diamonds qui sont intéressants parce qu'ils nous offrent dores et déjà des foncteurs polymorphiques .

    Et justement, ils risquent de m'aider pour la GUI sur laquelle je travaille actuellement (qui n'a rien à voir avec la GUI entièrement basée sur les std::function que j'ai laissée tomber bien que résolvant tous les problèmes de mise à jour de valeurs dynamiques). Je me retrouve avec des macros parce que je ne pouvais pas passer des fonction template en tant que foncteur... ce que vise à résoudre cette technique pour laquelle je remercie S.T.L. d'en avoir montré les ficelles .

  8. #8
    Membre confirmé

    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2012
    Messages
    108
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Etudiant
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Juillet 2012
    Messages : 108
    Points : 573
    Points
    573
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Fort heureusement mes lambdas sont toujours assez courtes et simples pour qu'il soit plus rapide/facile de les écrire sans aller voir ailleurs ^^ (vite écrit quand on a le coup de main ).

    Ce sont les foncteurs diamonds qui sont intéressants parce qu'ils nous offrent dores et déjà des foncteurs polymorphiques .
    Pour la question, qu'on se posait dernierement, j'ai bien la confirmation les diamonds ont été voté et seront donc implémentés, on a donc dépassé le stade de la proposition pour C++14

    D'ailleurs elles seraient dans le pdf N3690 du draft concernant les features du C++14.
    Le paradigme de chacun ne dépend pas de lui, mais de son éducation...

    Le mot donne à la pensée son existence la plus haute et la plus noble.
    Spinoza

    Quiconque n'est pas choqué par la théorie quantique ne la comprend pas.
    Niels Bohr

    http://isocpp.org/

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Fort heureusement mes lambdas sont toujours assez courtes et simples pour qu'il soit plus rapide/facile de les écrire sans aller voir ailleurs ^^ (vite écrit quand on a le coup de main ).
    Peu importe leur taille, le gros reproche qu'on peut leur faire (non, le terme est trop fort, mettons : le gros point contre lequel il faut mettre le développeur en garde) est, justement, la raison pour laquelle elles ont été créées : le fait que le scope dans lequel la lambda est accessible est excessivement localisé.

    Je m'explique:

    Imaginons que tu aies une classe A qui prenne la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class A{
        public:
        long long id() const;
    };
    Tu as une classe spécifique qui contient une collection de A, sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class AHolder{
     
    private:
        std::vector<A> tab; // ou n'importe quel autre type de collection ;)
    };
    Jusque là, il n'y a pas de problème

    Puis tu rajoutes une fonction à ta classe AHolder sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class AHolder{
    public:
       /* ... */
         void foo(){
            std::sort(tab.begin(),tab.end(),
                      [](const A & a, const A & b) -> bool{return a.id() <b.id();});
            /* ... */
        }
    private:
        std::vector<A>; // ou n'importe quel autre type de collection non triée;)
    };
    Jusque là, je n'ai toujours aucun problème.

    Mais ca va commencer à se corser avec l'ajout de bar, qui nécessite aussi de trier la collection...

    On risque en effet, si le développeur est distrait (ou qu'il n'y pense pas ou que... va savoir quoi) de se retrouver avec quelque chose comme
    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
    class AHolder{
    public:
       /* ... */
         void foo(){
            std::sort(tab.begin(),tab.end(),
                      [](const A & a, const A & b) -> bool{return a.id() <b.id();});
            /* ... */
        }
         void bar(){
            std::sort(tab.begin(),tab.end(),
                      [](const A & a, const A & b) -> bool{return a.id() <b.id();});
            /* ... */
            }
        }
    private:
        std::vector<A>;
    };
    Avec un peu de chance, les deux lambda seront assez proche dans le code pour que le développeur se rende compte qu'il a écrit strictement deux fois la même expression, et pour qu'il factorise un peu cela sous la forme de
    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
    class AHolder{
    public:
       /* ... */
         void foo(){
             sortById();
             /* ... */
        }
         void bar(){
             sortById();
             /* ... */
        }
    private:
        void sortById(){
     
            std::sort(tab.begin(), tab.end(), 
                [](A const & a, A const & b)->bool{ return a.id() <b.id(); } );
        }
        std::vector<A>;
    };
    Ce qui aurait pour effet de rendre l'utilisation raisonnable, car elle évite la duplication de code.

    Mais voilà qu'apparait une nouvelle classe : B, qui n'a strictement rien à voir avec A, à part que, elle aussi, elle expose une fonction id() sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class B{
        public:
        long long id() const;
    };
    Et bien sur, on a un holder qui va avec, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class BHolder{
       /* ... */
    private:
        std::vector<B> tab; // ou n'importe quel autre type de collection ;)
    };
    Les deux domaines sont tellement séparés qu'il n'y a aucune chance pour que A et B (ou pour que AHolder et BHolder) soient utilisés ensemble.

    Mais, en cours de développement, tout ce que j'ai fait subir à AHolder, je peux le faire subir à BHolder. Comprends par là que je peux y ajouter deux fonctions qui, dans une première version, utiliseraient chacune sa propre expression lambda et que je peux décider de la factoriser (en donnant d'ailleurs sans doute le même nom de sortById).

    C'est très bien, si l'on regarde AHolder et BHolder pris séparément.

    Mais ca commence vraiment à me poser un problème si l'on envisage le projet dans son ensemble : nous nous retrouvons en effet avec du code dupliqué et strictement identique, et ca, ca devient dangereux parce que, s'il advient que tu as fait une erreur de logique, il y a de fortes chances pour que tu l'aies reproduite aux deux endroits.

    Et, dans ce cas, tu finiras tôt ou tard te rendre compte que sortById ne donne pas le résultat escompté, peut être pour AHolder ou peut être pour BHolder, et tu apporteras la correction nécessaire... Pour la classe pour laquelle tu as remarqué l'erreur, en oubliant (très vraisemblablement) qu'il faut corriger cette erreur à deux endroits.

    Bien sûr, on pourra me dire que ce n'est pas avec un simple test comme a.id() < b.id() que l'on risque de se tromper, mais qu'en est-il des expressions lambda "à peine plus compliquées"

    Ecrire une expression lambda lorsque l'on a la certitude qu'il ne faudra pas la dupliquer n'est pas une mauvaise chose en soi. Mais peut-on vraiment avoir cette certitude

    Tu me répondras sans doute que rien ne t'empêche d'écrire une première version qui utilise une expression lambda et de la transformer en foncteur (c'est si simple ) si tu te rend compte que tu commence à la dupliquer, et je t'accorderai ce point, à un bémol près : es-tu sur que tu te souviendras d'avoir déjà écrit la même expression dans une semaine ou dans six mois
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Je vais être audacieux, et répondre autre chose :
    Si les codes identiques ne sont pas suffisamment proches pour que je les remarque, alors il n'y a pas de raison de factoriser. Oui, à trop vouloir factoriser, on perd son temps (sans parler de factoriser d'avance, mais tu connais mon opinion là dessus ^^), c'est comme l'over-généricité. En effet, si comme tu le dis ton A et ton B n'ont aucune raison d'être manipulées dans un même morceau du programme, faire une factorisation entre les deux ne rime à rien : peut-être voudrai-je modifier A plus tard, je ne voudrais pas que cela affecte B, hop je me retrouve avec un niveau d'abstraction inutile pour B.

    Parfois un peu de duplication n'est pas mauvaise. Tant qu'elle n'est pas faite sciemment, et que les codes sont indépendants. Pour ce qui est des bouts de code à utilité générale, ça va dans votre extension STL perso, et ça ça mérite d'être factorisé à l'échelle du projet.

  11. #11
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Je vais être audacieux, et répondre autre chose :
    Si les codes identiques ne sont pas suffisamment proches pour que je les remarque, alors il n'y a pas de raison de factoriser. Oui, à trop vouloir factoriser, on perd son temps (sans parler de factoriser d'avance, mais tu connais mon opinion là dessus ^^), c'est comme l'over-généricité. En effet, si comme tu le dis ton A et ton B n'ont aucune raison d'être manipulées dans un même morceau du programme, faire une factorisation entre les deux ne rime à rien : peut-être voudrai-je modifier A plus tard, je ne voudrais pas que cela affecte B, hop je me retrouve avec un niveau d'abstraction inutile pour B.
    Justement, c'est là que je ne suis pas d'accord : une abstraction n'est jamais inutile.

    Elle peut sembler l'être à un moment donné, mais, dis toi que si tu as utilisé une abstraction pour comparer tes id parce qu'une classe en avait besoin c'est que tu peux très bien en avoir besoin ailleurs.

    Tu veux modifier tes A pour qu'ils soient triés en fonction de leur nom Rien de plus facile : tu crées un autre foncteur (lessByName) et tu l'utilises uniquement là où tu en as besoin.

    On peut encore discuter sur l'intérêt de le faire pour deux classes, mais si au final c'est dix, quinze ou vingt classes qui sont manipulées, qui doivent être triées parfois en fonction d'un nom, parfois en fonction d'un id ou que sais-je, le fait d'avoir une abstraction qui te permet de le fait t'autorise, justement, à jongler avec ces abstractions bien plus facilement que ton expression lambda.

    Je ne nie absolument pas l'intérêt des expressions lambda, je dis juste qu'elle doivent, comme toute technique avancée de programmation, être utilisées à bon escient.

    Et il n'y a rien à faire, à mon sens, le "bon escient" se trouve être en l'occurrence un comportement à ce point spécialisé qu'il n'y a strictement aucune chance pour que l'on en vienne à dupliquer le code d'une manière ou de l'autre

    Maintenant, c'est mon avis strictement personnel (qui semble malgré tout partagé par S.T.L ) et je ne t'obliges absolument pas à être d'accord
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #12
    En attente de confirmation mail

    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 : 33
    Localisation : France, Doubs (Franche Comté)

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

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

    Je n'ai lu toute la conversion qu'en diagonale, mais la verbosité des lambda c'est à cause du choix de syntaxe du C++, pas de la notion de lambda. Le greater devient avec phoenix (pour rester dans du C++,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    \x y -> x > y
    -- Ou plus rapide
    '>'
    en Haskell).

    Edit: Après qu'on décide de nommer un fonction/objet greater dans une section où l'on en a besoin plusieurs fois, pourquoi pas. De là à ce que ce soit un élément du langage ... je trouve qu'il y a un fossé.

  13. #13
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Là où je voulais en venir, c'est que la factorisation doit comme le reste être scopée. Autrement on se retrouve avec une pollution du scope par une entité qui se ballade, qui n'a rien à faire là excepté qu'on n'a pu faire autrement que la mettre au juste milieu entre les deux entités qu'elle factorise.

    Le LessByName par exemple, est à mon sens une très mauvaise factorisation. En effet, name pourrait être n'importe quoi, le nom d'un chat comme le nom d'un fichier. En outre ça pourrait comparer des fonctions membres comme des fonctions libres, comme des attributs, par pointeur interposé ou non, on n'en sait rien, et si on a le besoin d'un autre de ces choix d'implémentation que celui arbitrairement choisi par l'implémenteur de LessByName, on est foutus, le nom est déjà pris.

    En revanche, et c'est là que je regrette très fortement que les macros ne puissent être scopées ou que les templates ne puissent être de véritables macros, si on pouvait faire sort(begin(v), end(v), std::less_by_attribute(name)); , mais quel royaume serait Caamelot mes amis ! eh bien je serais ravi de l'utiliser.

    Tiens, une petite proposition de syntaxe pour des template macros qui vient de me traverser l'esprit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<macro M>
    void f()
    {
        M...;
    }
     
    f<R"(du code)">();
    Pour le trop d'abstractions, justement S.T.L. nous met en garde contre l'abus de couches, car le compilo à un certain point n'est plus capable de les éliminer automatiquement quand elles sont là pour faire beau.

    Et pour le coup je suis d'accord avec Flob.

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    Là où je voulais en venir, c'est que la factorisation doit comme le reste être scopée. Autrement on se retrouve avec une pollution du scope par une entité qui se ballade, qui n'a rien à faire là excepté qu'on n'a pu faire autrement que la mettre au juste milieu entre les deux entités qu'elle factorise.
    si ton foncteur est générique, tu te fous royalement de sa position!

    Il pourrait tout aussi bien être dans un dossier totalement étranger à ton projet, vu que tu t'abstrais totalement du type de la donnée qu'il manipule : c'est un typename T.

    La seule chose qu'il faut, c'est que le T en question expose une fonction bien précise (id(), name() ou tout ce que tu veux) qui renvoie une valeur qui puisse être utilisée dans une comparaison "plus petit que".

    Le LessByName par exemple, est à mon sens une très mauvaise factorisation. En effet, name pourrait être n'importe quoi, le nom d'un chat comme le nom d'un fichier. En outre ça pourrait comparer des fonctions membres comme des fonctions libres, comme des attributs, par pointeur interposé ou non, on n'en sait rien, et si on a le besoin d'un autre de ces choix d'implémentation que celui arbitrairement choisi par l'implémenteur de LessByName, on est foutus, le nom est déjà pris.
    Justement, non.

    tu crées un foncteur LessByName parce que tu te rends compte que tu vas l'utiliser sur des classes qui ont toutes le point commun de présenter une fonction name().

    A partir de là, tu te fous royalement de savoir à quoi correspond ce nom : ce qui importe, c'est que la valeur (quel qu'en soit le type) renvoyé par cette fonction soit comparable, point barre.

    Après, s'il advient que tu te rende compte que ce n'est pas du tout par le nom que tu veux comparer tes objets, ben, tu choisi un autre comparateur (LessByDirName, si tu veux et si ca correspond mieux à tes besoin )
    Pour le trop d'abstractions, justement S.T.L. nous met en garde contre l'abus de couches, car le compilo à un certain point n'est plus capable de les éliminer automatiquement quand elles sont là pour faire beau.

    Et pour le coup je suis d'accord avec Flob.
    Attends, ici, on parle d'ajouter une couche, uniquement dans les fonctions qui en ont besoin, qui se contente d'appeler les accesseurs qui vont bien pour un traitement particulier.

    Evidemment, si a.name() fait appelle à b_.name() qui fait appelle à c_.name() qui fait appel à d_.name() qui fait appel à e_.name() (c'est chaque fois un membre de la classe en question hein ) le problème ne viendra pas de ton foncteur, mais bien de la manière tu as structuré tes données.

    Il faudra sans doute commencer par ce demander pourquoi le nom d'un objet de type A devrait être celui de l'objet qui est manipulé par le membre d'un membre d'un membre d'un membre, tu ne crois pas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par germinolegrand Voir le message
    En revanche, et c'est là que je regrette très fortement que les macros ne puissent être scopées ou que les templates ne puissent être de véritables macros, si on pouvait faire sort(begin(v), end(v), std::less_by_attribute(name)); , mais quel royaume serait Caamelot mes amis ! eh bien je serais ravi de l'utiliser.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define less_by(T, name)	\
    [](const T &lhs, const T &rhs)->bool { \
    	return lhs. name < rhs. name ; \
    }
     
    struct Foo {
    	int attr;
    	int getAttr() const { return attr; }
    };
     
    std::vector<Foo> vec;
    std::sort(vec.begin(), vec.end(), less_by(Foo, getAttr()));
    std::sort(vec.begin(), vec.end(), less_by(Foo, attr));
    Ca fait par contre plus de 2 heures que j'essaie de faire marcher cette syntaxe
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::sort(vec.begin(), vec.end(), less_by(Foo, getAttr));
    std::sort(vec.begin(), vec.end(), less_by(Foo, attr));
    mais je vois pas du tout comment faire, si quelqu'un trouve une solution..
    (ce à quoi je suis arrivé, qui ne compile pô)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #define less_by(T, name)	\
    [](const T &lhs, const T &rhs)->bool { \
    	struct foo { \
    		static decltype(lhs. name ) get(const T &t, int) { \
    			return t. name ; \
    		} \
    		static decltype(lhs. name ()) get(const T &t, long) { \
    			return t. name (); \
    		} \
    	}; \
    	return foo::get(lhs, 0) < foo::get(rhs, 0); \
    }

  16. #16
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Parenthèse, S.T.L. a énoncé un certain nombre de règles/conseils assez intéressants (le genre à mettre dans les docs qualités de vos boites au chapitre C++11 si c'est une annexe, ou au bon endroit directement sinon) qui m'ont marqué.

    * Ne retournez pas par valeur constante
    -> car cela bloque la sémantique de déplacement, et cela n'apporte rien de bien utile.
    (cela invalide une astuce donnée dans un des GOTW, IIRC)

    * N'utilisez pas move() pour retourner une variable locale par valeur
    -> la NRVO et la sémantique de déplacement sont conçues pour travailler main dans la main
    -> quand la NRVO est applicable, la construction directe est optimale
    -> dans le cas contraire, la sémantique de déplacement est efficace (et activée donc)

    * Ne retournez pas par rvalue reference
    -> pour les experts seulement, besoin rare
    -> Même le comité de standardisation s'est brulé les ailes
    -> quelques exemples valides toutefois : forward, move, declval, get(tuple&&)

    * Reposez-vous sur la déduction automatique des arguments templates
    * Evitez d'expliciter des arguments template, sauf si nécessaire
    (des règles clients que j'ai auditées dernièrement exigeaient le contraire, car "ça serait plus clair")

    * Utilisez make_unique (C++14), make_shared, make_pair, ...
    * Utilisez nullptr
    * Ne pas surtyper (ce terme de moi, je crois, et il ne me plait pas du tout il y a certainement mieux) pour rien (i.e. string("foo") + s + string("bar"))
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  17. #17
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #define less_by(T, name)	\
    [](const T &lhs, const T &rhs)->bool { \
    	return lhs. name < rhs. name ; \
    }
    Beark, une macro

    Par contre, l'idée est bonne. Sean Parent en parle dans son talk "seasonning", sous le nom "interface symmetry" (vers 29 minutes). Et dans ASL, ça a l'air d'être disponible. J'ai l'impression qu'il se base sur le fait que bind peut transformer un pointeur sur membre en foncteur, mais je n'ai pas regardé dans les détails.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

Discussions similaires

  1. Réponses: 0
    Dernier message: 15/10/2010, 14h25
  2. Don't flush the Session after an exception occurs
    Par yohan_al dans le forum Hibernate
    Réponses: 1
    Dernier message: 31/03/2009, 15h57
  3. Réponses: 2
    Dernier message: 23/01/2008, 16h26

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