Journal Jouons avec le ``switch`` et C++17

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
27
5
mar.
2018

Et non, ceci n'est pas un journal sur la switch, juste un partage rapide et amusé d'une version alambiquée mais qui fait tourner la tête (comme beaucoup de choses sortant d'un alambic, n'est ce pas ?):

template<unsigned N>
__attribute__((noinline)) void stuff()  {}

// manual switch
void manual_switch(int i) {
    switch(i) {
        case 0: return stuff<0>();
        case 1: return stuff<1>();
        case 2: return stuff<2>();
        case 3: return stuff<3>();
        default: return stuff<100>();
    }
}

ce code a l'air de rien. On peut imaginer que stuff ait une bonne raison d'être template, et le switch est là pour passer du monde dynamique au monde statique.

Je vous propose une version équivalente en C++17 :

namespace bits {
template<std::size_t... N>
void meta_switch(int i, std::index_sequence<N...>) {
    ((i==N && (stuff<N>(), true)) || ...) || (stuff<100>(), true);
}
}

void meta_switch(int i) {
    return bits::meta_switch(i, std::make_index_sequence<4>());
}

Premier constant : c'est pas ultra lisible. mais ça fait parti du charme (de l'hètre ?).

On notera en vrac et dans le désordre :

  • l'utilisation d'une fold expression , cru 2017, pour chainer les conditions
  • l'évaluation paresseuse des opérateurs && et || pour gérer le default et le chaînage
  • le vieux trick (stuff<N>(), true) qui ne crée par un tuple mais évalue stuff<N> puis renvoie true pour palier à l'absence de valeur de retour de stuff<N>

Mais la chose la plus savoureuse, c'est l'assembleur généré par clang dans ces deux cas, que je vous livre sur godbolt. C'est exactement le même, ce qui est quand même poilant (poil aux dents).

Bref, le C++, c'est parfait pour l'ivresse du soir !

  • # Namespace bits ?

    Posté par  . Évalué à 1.

    Y a-t-il une bonne raison de le mettre dans le namespace bits ? Ou bien c'est pour bien ranger le code ?

    Sinon, oui, c'est pas lisible. M'enfin, le rôle d'une bonne bibliothèque, c'est d'être lisible à l'utilisation, pas à la lecture du code. D'où une bonne doc ;)

    • [^] # Re: Namespace bits ?

      Posté par  (site web personnel) . Évalué à 2.

      Ou bien c'est pour bien ranger le code ?

      C'est ça !

      • [^] # Re: Namespace bits ?

        Posté par  (site web personnel) . Évalué à 2.

        Par contre je suis étonné que clang ne soit pas capable de mélanger les morceaux de code identiques:

        id stuff<1u>(): # @void stuff<1u>()
          ret
        void stuff<2u>(): # @void stuff<2u>()
          ret
        void stuff<3u>(): # @void stuff<3u>()
          ret
        void stuff<100u>(): # @void stuff<100u>()
          ret

        C'est stressant quand à l'impact sur les performances que les templates peuvent avoir (duplication d'assembleur -> trash du cache d'instruction)

      • [^] # Re: Namespace bits ?

        Posté par  (site web personnel) . Évalué à 1.

        D'ailleurs, il me semble bien qu'avec des namespaces anonymes on peut faire des choses pas mal aussi :D

    • [^] # Re: Namespace bits ?

      Posté par  (site web personnel) . Évalué à 10.

      M'enfin, le rôle d'une bonne bibliothèque, c'est d'être lisible à l'utilisation, pas à la lecture du code.

      Je ne suis pas d'accord du tout, tu passes plus de temps à lire du code qu'à en écrire, une API pas lisible c'est la garantie que ceux qui ont moins d'expérience que toi s'arracheront les cheveux (ou même toi dans 6 mois quand ça te sera sorti de la tête).

      • [^] # Re: Namespace bits ?

        Posté par  . Évalué à 8.

        C'est un peu la reflexion que je me faisais mais j'en suis venu a la conclusion que ce genre de syntaxe permet aux devs C++:

        • d'etre indispensable
        • de briller en societe
        • de justifier un refacoring aux clients

        Parceque en dehors de cela je ne vois vraiment pas l'interet d'ecrire des trucs tellement sioux que personne ne sera a meme de comprendre sans une semaine d'analyse 6 mois apres l'ecriture du code (y compris le devs qui aura ecrit ca).

        • [^] # Re: Namespace bits ?

          Posté par  (site web personnel) . Évalué à 4.

          de briller en societe

          complètement !

          On peut aussi voir ça comme un exercice de style, une sorte de jeu, ce qui est le cas de ce journal :-)

        • [^] # Re: Namespace bits ?

          Posté par  . Évalué à 10.

          Bah, techniquement, c'est quand même faux. Les syntaxes hyper-complexes du C++ moderne permettent la méta-programmation, avec derrière l'idée de remplacer le polymorphisme (qui a un coût à l'exécution) par la programmation générique (qui calcule tout à la compilation).

          Apparemment, pour certains développeurs, ce changement de paradigme, pour des raisons théoriques (calculer le maximum de choses à la compilation) et pratiques (gains de performance), justifie l'usinagazisation du C++ et l'imbittabilitabilisation du code. Ces développeurs sont assez influents pour infléchir la modification du standard dans ce sens, ce qui veut aussi dire que les gros acteurs économiques du développement C++ vont aussi dans ce sens. Parmi les éléments moteurs, il y a par exemple les développeurs des compilateurs, et les développeurs de bibliothèques (stl, boost…).

          Reste que j'ai parfois l'impression d'utiliser C++ dans un contexte tellement différent de ceux qui font de la méta-programmation qu'on n'a pas l'impression d'utiliser le même langage. Une grosse partie du C++17 permet de créer un méta-langage, issu d'une sorte de mélange entre le pré-compilateur C et du C++ perché, qui n'est pas destiné au commun des mortels. D'ailleurs, et ça ne date pas du C++17, de nombreux logiciels imposent l'utilisation d'un C++ allégé (sans templates, sans héritage multiple, sans pointeurs nus…) à leur développeurs, et vu la tronche du C++ 17, c'est pas près de changer. Le risque, c'est évidemment que les concepteurs du C++ se coupent de la base. D'un côté, on peut très bien dire que les dialectes simplifiés du C++ sont viables, et qu'on peut très bien décider d'ignorer complètement les templates variadiques et les constexpr. Mais il existe aussi un risque qu'on décide aussi de se passer du C++ tout court, histoire par exemple de pouvoir relire le code qu'un dev un peu trop spécialiste a pondu.

          Pour l'utilisation que j'en ai, par exemple, la programmation générique n'a que peu d'intérêt (en dehors de l'utilisation des bibliothèques qui les utilisent) ; la perte de la relation logique d'héritage me semble même rédibitoire. La tendance à utiliser les constexpr systématiquement me fait aussi froid dans le dos, avec en filigrane le retour des magic-values déguisées en constexpr dans le code plutôt que la lecture de l'ensemble dans paramètres dans un fichier (ainsi que l'instantiation des arrays à la compilation, etc). Au delà de la syntaxe ésotérique, il y a donc aussi le problème qu'à force d'être multiparadigme, C++ s'étale tellement qu'on peut légitimement se demander si c'est encore un langage de programmation.

          • [^] # Re: Namespace bits ?

            Posté par  . Évalué à 4.

            On peut faire du code lisible en perl, on peut fait du code lisible en c++17, on peut faire du code lisible en python; l'inverse est aussi vrai, c'est plus facile selon les langages;

            Les folding expression sont une simplification de ce qu'il est possible de faire avec les variadic templates.

            les variadic template permettent dans pas mal de cas une simplification d'écriture et un code plus clair. Et cela faisait partie du package C++11 qui lui a vraiment donné un coup de jeune au langage, permettant de le rendre beaucoup plus lisible, et facile à coder.

            de nombreux logiciels imposent l'utilisation d'un C++ allégé (sans templates, sans héritage multiple, sans pointeurs nus…)

            • sans templates ? pas de std::map ? pas de std::vector? pas de sort? pas de std::string? pas de shared_ptr ? pas de pair/tuple, pas de sort, pas de fill

            • sans héritage multiple je peut comprendre, ça peut être perturbant, mais c'est bien pratique.

            • sans pointeurs nus, arf, donc pas de pointeur du tout, vu que pas de template… Moi j'ai une politique plus pragmatique pas d'écriture de new (et donc pas de delete) sauf cas très particulier, et commenté. Que des make_shared<> ou make_unique<>. Si un paramètre est un pointeur il est optionnel, sinon on passe une ref, depuis c++17 on a std::optional;

            Bref, a part les destructeurs et surcharge d'opérateurs, et la gestion des const, il ne reste plus grand chose de c++…

            les templates bien utilisés permettent d'éviter la duplication de code, et en améliorent la maintenabilité.

            Il ne faut pas décorner les boeufs avant d'avoir semé le vent

            • [^] # Re: Namespace bits ?

              Posté par  . Évalué à 4. Dernière modification le 08 mars 2018 à 09:51.

              sans templates ? pas de std::map ? pas de std::vector?

              Tu peux utiliser la STL sans maintenir toi-même de classes templates.

              sans pointeurs nus, arf, donc pas de pointeur du tout, vu que pas de template…

              On voit encore des projets assumés dans un langage appelé 'C/C++', qui est vu comme du C amélioré. Mais non, en général, à moins que quelqu'un ait une expérience contraire, j'ai rarement vu toutes les exclusions en même temps. J'ai aussi oublié 'sans exceptions', par exemple. Et j'imagine qu'avec C++14 et C++17, il risque d'y avoir encore plus de syntaxes proscrites (sans lambda, etc).

              les templates bien utilisés permettent d'éviter la duplication de code, et en améliorent la maintenabilité.

              Si tu parles de conteneurs, alors oui, certainement, dans une certaine mesure (mais c'est quand même difficile de ne pas trouver son bonheur dans la STL, si tu es amené à coder toi-même ton propre conteneur, c'est aussi peut-être que tu es en train de faire quelque chose de compliqué).

              En fait, (c'est un avis personnel qui est bien sûr hautement contestable), je n'ai jamais été emballé par l'idée de mettre autre chose que des types de base dans les classes templates. L'utilisation des templates comme alernative à la POO, je ne comprends pas l'intérêt (bien sûr, vtable etc., mais les cas où ça pose de vrais problèmes de perfs sont quand même assez restreints). J'ai même l'impression que la philosophie de la POO se perd complètement quand on adopte des pratiques basées sur les templates. Si tu crées une fonction qui prend une référence vers un objet de classe A, elle va aussi prendre tous les types dérivés de A, et seulement eux, c'est super clair. Si tu passes par des templates, alors n'importe quel objet qui passe la compilation (qui fournit des méthodes qui ont le même nom que ce qui est utilisé dans le template) peut être passé en argument, il n'y a plus aucune vérification de la logique voulue par le programmeur, je trouve ça absurde.

              Et en plus, comme je n'ai dit plus haut, la logique de déporter tout un tas de choses à la compilation me rappelle des centaines d'exemples de blocages à la con (pas possible d'avoir plus de 1000 lignes dans le tableur, pas possible d'avoir une résolution qui dépasse 1000px, etc) parce que quelqu'un avait comme un boulet instantié des tableaux avec des tailles constantes. C'est quelque chose qui avait complètement disparu en C++ avec l'avènement des conteneurs STL, et que je sens revenir rapidement avec la généralisation des constrexp et autres méthodes de méta-programmation où rien n'est dynamique, et où il va falloir recompiler à chaque fois qu'on veut changer un paramètre.

              • [^] # Re: Namespace bits ?

                Posté par  . Évalué à 5.

                si tu es amené à coder toi-même ton propre conteneur, c'est aussi peut-être que tu es en train de faire quelque chose de compliqué

                Hum… il suffit d'avoir besoin de performances ou de fiabilité. Avec la STL, il n'existe aucun moyen simple de diagnostiquer l'usage mémoire, ce qui implique qu'on n'est jamais trop sûrs quand le conteneur va nécessiter une réallocation et donc pourrir les itérateurs ou tout simplement que l'on ne peut savoir combien de mémoire une collection consomme (amuses-toi à calculer la RAM bouffée par une map…).

                Sinon, la STL n'implémente aucun conteneur pour gérer les chaînes de caractères: std::string se contente de gérer des chaînes d'octets supposément caractères ASCII, ou éventuellement des wchar.

                Il n'y a aucun ring buffer non plus.

                Les tableaux associatifs sont habituellement implémentés en tant que RBTree, consommateurs en mémoire (une chiée de pointeurs sur 8 octets, donc de très probables cache miss en cascade).

                Pas moyen de gérer une destruction automatique de ressource qui ne soit pas de la RAM. Pas moyen de gérer la destruction de RAM allouée par une lib utilisant le modèle hourglass sans overhead (hourglass: passer par une API «simple» sans exceptions ni RTTI pour facilité la stabilité d'ABI, par exemple pour un mécanisme de plugins).

                Mais bon, je suis d'accord, tant qu'on veut juste faire la fonctionnalité en prototype, la STL dispose de tout ce qu'il faut.

                J'ai même l'impression que la philosophie de la POO se perd complètement quand on adopte des pratiques basées sur les templates.

                Pour moi, les deux approches sont complémentaires. Tu cites les conteneurs standards, et justement ils montrent la complémentarité: sans eux, en C++, on serait obligé de soit réimplémenter les conteneurs pour chaque type, soit les implémenter une seule fois et que chaque noeud prenne un void* et un void_free( void* ). Outre le problème de la RAM, il y a le problème de la type safety donc.

                Quand je code en C++, j'utilise des classes, de l'héritage, des templates, des fonctions… en fonction de ce dont j'ai (supposément) vraiment besoin.

            • [^] # Re: Namespace bits ?

              Posté par  . Évalué à 5.

              de nombreux logiciels imposent l'utilisation d'un C++ allégé (sans templates, sans héritage multiple, sans pointeurs nus…)

              Ils en interdisent rarement la totalité des exemples, mais un jeu (encore que, je n'ai jamais vu les pointeurs nus être interdits).

              Perso, je sais que dans mes projets j'essaie d'éviter d'utiliser les exceptions par exemple: tout bêtement parce qu'il n'existe aucun moyen à ma connaissance de s'assurer qu'elles sont toutes gérées correctement (non, try{}catch(...){abort();} après 10 niveaux d'encapsulation ou rien n'est attrapé, ce n'est pas une façon correcte de le faire), et que donc ça reviens à un printf("j'ai glissé chef!\n");abort(); (voire pire, parce qu'on ne sait même pas dans quel fichier et à quel ligne les choses ont commencé à merder).
              Mais pas de bol, le C++ malgré son "you pay for what you use" ne fournit aucun conteneur «semi-dynamique» (qui ne grossisse pas de façon implicite, histoire que l'on ait une chance d'éviter les crash parce que le kernel à attribué de la mémoire qu'il n'a pas réservée en réalité) ou exception-free, donc si je veux utiliser la STL, je l'ai dans l'os (sauf à ré-implémenter mes propres conteneurs, forcément, ce qui n'est pas nécessairement difficile mais pénible) sur ce point.
              Du coup, bah je n'utilise pas les exceptions tout en sachant que mon code peut exploser parce que les outils dont je dépend eux en utilisent éventuellement (ça peut être une lib qui en utilise une alors que la doc n'a pas été mise à jour, par exemple).

              les templates bien utilisés permettent d'éviter la duplication de code, et en améliorent la maintenabilité

              J'invoque les 2 implémentations de la STL (seul outil dont je ne puisse me passer qui utilise réellement les template à fond) avec lesquels j'ai le plus travaillé en contre-exemple: libstdc++ et libcpp.

              Tu serais vraiment très, très fort si tu arrivais à me convaincre que ces trucs sont maintenables grâce aux templates (avec mention spéciale pour la libstdc++ qui est vraiment, vraiment dégueulasse, un simple appel à size() fait passer par une chiée de fonctions dont l'indentation est un mélange d'espaces et de tabulations!).

              Il me faut aussi mentionner le fait que les compilateurs que j'utilisent (g++ et clang) optimisent même en -O0 -g les templates, rendant ingérable le debug sans connaître les mécanismes internes (bien qu'il faille reconnaître ici qu'au moins libstdc++ fournit une solution de contournement pour gdb, si on a python3 disponible) ainsi que le fait que l'endroit qui génère une erreur de compilation est toujours noyé dans 2 pages de diagnostic (au moins, avec clang, on arrive à s'y retrouver grâce aux couleurs, mais ça reste sous-optimal) qui sont souvent tout sauf pertinents.

              Alors, c'est vrai, tu as précisé «bien utilisés», mais du coup, aurais-tu des exemples de code, post C++11 (enfin, si t'en trouves du pré C++11 tu auras encore plus mon respect) qui n'ait pas ces problèmes d'utilisation?

              Honnêtement, dans le fond, je suis relativement d'accord (j'ai quelques utilitaires templates qui sont bien utiles pour libérer automatiquement des ressources sans overhead (chose que la STL ne sait pas faire), mais sincèrement, selon moi pour qu'un template rende du code maintenable il ne faut pas abuser des fonctionnalités qu'ils offrent. Avec mention spéciale pour la récursion et donc les variadic. Je ne dis pas que ça n'a aucun intérêt, mais quand on on arrive à faire ça pour un switch, à part pour le fun, j'ai un sérieux doute.
              Peut-être que le switch est plus lisible, mais si pour détecter un bug même trivial il faut passer 2 heures à éplucher des indirections…

              *: au sujet des conteneurs pénibles:

              • devoir implémenter operator== quand operator!= l'est (pour rappel: il y 10 opérateurs de comparaison au total, qui peuvent s'implémenter avec 2 seulement!), et le seul intérêt «réel» de les implémenter séparément que je vois, c'est pour obfusquer le code et le rendre plus buggué, peut-être pour un futur IOCPPCC?
              • idem pour les divers opérateurs d'accès dans leurs formes const/non-const
              • idem pour les itérateurs, bien que le jour ou je suis tombé sur un article mentionnant l'idée de passer un paramètre template booléen pour la constance à changé ma vie. Je ne m'emmerde jamais à utiliser et encore moins implémenter les reverse iterators, j'ai autre chose à faire qu'écrire du boilerplate après tout.
              • [^] # Re: Namespace bits ?

                Posté par  . Évalué à 6.

                devoir implémenter operator== quand operator!= l'est

                Ça n'est pas un problème complètement général avec les surcharges d'opérateurs? La surcharge d'opérateurs permet principalement l'obfuscation, les bugs cryptiques, et les idées foireuses (par exemple, inventer des opérateurs connus de soi seul pour la concaténation). S'assurer de la cohérence d'une série de (+, +=, ++, -, -=, --, *, *=, /, /=, l'unary -() ), c'est bien galère.

                • [^] # Re: Namespace bits ?

                  Posté par  . Évalué à 4.

                  C'est vrai, mais je pense que le problème n'est pas lié au mécanisme de surcharge des opérateurs en lui-même (les fonctions à la con derrière des noms débiles, ça existe aussi après tout), qui permets des choses plutôt sympa (concaténer des chaînes de caractères, additionner des vecteurs mathématiques, déréférencer un pointeur intelligent, par exemple) mais du fait qu'il n'est pas possible d'implémenter plusieurs opérateurs d'un coup, ou qu'il n'est pas possible à ma connaissance que le compilo génère le code des opérateurs si des méthodes pré-définies existent déjà (et n'allez pas me dire que ça casserait la compat', ils l'on fait pour std::begin, std::end, etc…).

                  l'unary -() ), c'est bien galère.

                  Tu n'as pas mentionné operator,() :)

                  • [^] # Re: Namespace bits ?

                    Posté par  . Évalué à 3.

                    le compilo génère le code des opérateurs si des méthodes pré-définies existent déjà

                    J'ai peur que ça soit super complexe si tu veux un truc cohérent. Par exemple, si tu définis < et >, tu pourrais générer automatiquement tous les opérateurs de comparaison, mais c'est super dangereux, parce que si tu t'es foiré et que tu peux parfois être à la fois < et >, toute la série devient incohérente, et j'ai du mal à imaginer comment le comportement pourrait être défini (à moins de définir hyper strictement la logique que devrait suivre le compilo dans tous les cas de figure (si tu fournis < et >, si tu fournis == et >, si tu fournis <= et !=, etc). ).

                    En fait, ce qu'il manque pour en arriver là, c'est probablement d'associer une sémantique forte avec les opérateurs. La surcharge d'opérateurs te permet de faire n'importe quoi, l'opérateur est juste un nom de méthode presque comme un autre. Si tu définis que par défaut, ++ est équivalent à += 1; que *= x est équivalent à /= (1/x); tu imposes une sémantique, et ça n'a pas de limite (on pourrait aussi imposer qu'une méthode size() revoie un site_t…). Si ça ne tenait qu'à moi, ça ne me poserait pas de problème, mais je ne pense pas que ça soit comme ça que la surcharge d'opérateurs a été concue…

                    • [^] # Re: Namespace bits ?

                      Posté par  . Évalué à 2.

                      Ou sinon, ça peut se faire avec une classe abstraite qui implémente tous les opérateurs à partir de 2 opérateurs qui restent abstraits. Ça demande pas de modifier le langage et c'est facile de comprendre le comportement ? (Zut c'est juste de l'objet ^_^).

                      Si vraiment il faut je suis sûr que le C++ peut nous trouver un inline de constexpr static pour recopier les méthodes d'une classe mère vers sa fille pour éviter de passer par une vtable.

                      • [^] # Re: Namespace bits ?

                        Posté par  (site web personnel) . Évalué à 4.

                        Ça s'appelle Boost.operators non ?

                        • [^] # Re: Namespace bits ?

                          Posté par  . Évalué à 2.

                          Précisément et je me demande pourquoi chercher à demander au compilateur d'implémenter ça.

                          PS : boost a changer de site, il est sympa :)

                          • [^] # Re: Namespace bits ?

                            Posté par  . Évalué à 2.

                            Précisément et je me demande pourquoi chercher à demander au compilateur d'implémenter ça.

                            Ça pourrait être pour des raisons de performances, par exemple.

                            Le compilateur implémente déja un certain nombre de choses par défaut, ça ne semble pas évident qu'un constructeur de copie soit moins trivial ou plus important que de s'assurer que l'opérateur != renvoie NOT(==).

                            L'autre raison, c'est peut-être que s'il faut se taper la doc de boost et une dépendance supplémentaire, autant se taper l'implémentation de l'opérateur manquant.

                            • [^] # Re: Namespace bits ?

                              Posté par  . Évalué à 2.

                              Le compilateur implémente déja un certain nombre de choses par défaut, ça ne semble pas évident qu'un constructeur de copie soit moins trivial ou plus important que de s'assurer que l'opérateur != renvoie NOT(==).

                              Continuellement inventer des cas particulier au compilateur c'est ce qui rend déjà le C++ imbitable. Comme je disais plus haut quitte à ajouter une usine à gaz autant permettre à une méthode d'être dè-virtualisée en l'inlinant dans la classe fille. C'est plus générique et ce sera l'occasion d'ajouter une nouvelle syntaxe abscons au langage.

                              L'autre raison, c'est peut-être que s'il faut se taper la doc de boost et une dépendance supplémentaire, autant se taper l'implémentation de l'opérateur manquant.

                              Demander l'ajout de cette classe dans la bibliothèque standard c'est pas plus simple ?


                              Demander au compilateur d'avoir du sucre syntaxique pour des cas particuliers ne me semble pas une bonne idée (surtout quand il est possible de faire autrement).

                              • [^] # Re: Namespace bits ?

                                Posté par  . Évalué à 3.

                                La surcharge d'opérateurs, ça n'est pas un exemple typique de sucre syntaxique pour des cas particuliers? Le compilo fait déja plein de trucs en douce pour rendre l'écriture du code moins galère (typiquement, des cast automatiques dans tous les sens); remplacer a != b par !(a==b) quand l'opérateur != n'est pas défini (ou ++ par += 1), ça me semble nettement moins problématique que plein de trucs chelous autorisés par le standard (comme le court-circuitage du constructeur de copie).

                                Je ne doute pas que les gens qui écrivent le standard sont des gens très compétents, qui prennent des décisions après longue reflexion. J'ai juste l'impression que ces gens se servent d'abord eux-mêmes, et ont une vision hyper-technique du langage et de son évolution. Pour revenir au sujet du journal par exemple, c'est rigolo de pouvoir éviter un long switch par une utilisation alambiquée d'une syntaxe avancée de méta-programmation. Mais dans le même temps, tu gères encore à la main l'affectation et le constucteur de copie d'une classe qui contient 49 variables membres à copier telles quelles et un malheureux pointeur (c'est évidemment un problème de conception, mais ce problème de conception révèle un comportement étrange du standard).

                          • [^] # Re: Namespace bits ?

                            Posté par  . Évalué à 2.

                            Boost n'a pas changé de site. L'autre site est fait par un gars qui vend des bouquins.

      • [^] # Re: Namespace bits ?

        Posté par  . Évalué à 2.

        Alors, j'ai exprimé en raccourci, mais pour moi, lisible à l'utilisation veut dire que l'API est lisible. Par contre, l'implémentation peut être un peu tarabiscotée. Le but est que la bibliothèque doit rendre la vie plus facile, et s'intégrer le plus possible avec l'utilisation de l'appelant, et pas l'inverse. C'est un long débat, j'ai un peu la flemme.

    • [^] # Re: Namespace bits ?

      Posté par  . Évalué à 6.

      Si tu ne souhaites aucun contributeur pour ta bibliothèque c'est une bonne chose oui

      • [^] # Re: Namespace bits ?

        Posté par  . Évalué à 3.

        Certes. Ceci dit, si la bibliothèque apporte un vrai avantage, ça devrait suffire.

        J'ai déjà dû regarder le code de Boost pour comprendre certaines choses. Ça donne vraiment pas envie de contribuer à Boost. Mais on continue de l'utiliser, et certaines personnes y contribuent, parce que ça leur apporte quelque chose.

  • # Remarque

    Posté par  . Évalué à 2.

    Je trouve que gcc optimise bien mieux ce code.

  • # Lisibilité

    Posté par  . Évalué à 9.

    Je ne fait pas de C++ mais la première écriture est assez clair et compréhensible, la deuxième par contre c'est tout le contraire.
    Du coup si l'assembleur généré est le même ne vaut il mieux pas la première solution ?

    • [^] # Re: Lisibilité

      Posté par  . Évalué à -5.

      Pour la lisibilité ça dépend de l'habitude avec la syntaxe. AMHA les deux sont équivalentes en termes de lisibilité, et la première est plus lisible et plus rapide à écrire si on a 500 cas à générer.

      • [^] # Re: Lisibilité

        Posté par  . Évalué à 10.

        AMHA les deux sont équivalentes en termes de lisibilité,

        Je sais bien que tous les goûts sont dans la nature, mais franchement, je trouve que ce point de vue est très, très difficilement défendable. Voire que tu es en train de te foutre de nous, version «je suis un big boss du C++».

    • [^] # Re: Lisibilité

      Posté par  . Évalué à 3.

      Le switch case est à priori préférable car beaucoup plus lisible. Cependant il n'est pas extensible: s'il faut en faire une version à 5, une autre à 12, il faudra alors écrire manual_switch_5, manual_switch_12, etc., tandis que la version template fonctionne de base.

      Malgré cela je suis d'accord pour dire que dans le cadre d'un exemple aussi simple, écrire les différents manual_switch est sans doute encore préférable à la version template. Mais il s'agit d'un exemple simple, le code réel peut être beaucoup plus complexe, et il peut être intéressant de savoir écrire la version template.

      Et il est aussi intéressant de savoir que les compilateurs optimisent aussi bien les deux versions, le switch case étant un cas connu d'optimisation très agressive.

      • [^] # Re: Lisibilité

        Posté par  . Évalué à 5.

        Je vais surement dire une connerie mais si le code assembleur est exactement pareil comment la deuxieme solution peut etre plus generique que la premiere?

        • [^] # Re: Lisibilité

          Posté par  (site web personnel) . Évalué à 3.

          La deuxième est générique, et son instantiation sur le cas particulier du premier génère le même code.

          C'est pareil avec à peu près tous les cas de templates : C++ va te générer le même code pour vectro<int> que pour un vecteur d'entiers écrit à la main par exemple.

    • [^] # Re: Lisibilité

      Posté par  (site web personnel) . Évalué à 3.

      L(e seul) avantage du deuxième, c'est que tu peux augmenter le nombre d'éléments du switch à volonté, sans avoir à spawner les copier-coller :-)

    • [^] # Re: Lisibilité

      Posté par  . Évalué à 3.

      Quand tu en est rendu à écrire ce type de code, c’est que tu joues avec le système de type, peut être pour faire des vérifications assez avancées au moment de la compilations. Du coup ce qui compte c’est pas tant l’assembleur généré, ce code ne faisant absolument rien tu aurais tôt fait de ne rien écrire :) L’extensibilité de la solution mentionnée dans un autre commentaire prime sans doute alors, c’est sacrifier un peu de lisibilité dans la biblio pour pouvoir faire des vérifications poussées simplement sur le code client. Enfin j’imagine parce que j’ai foutrement aucune idée de à quoi une telle construction peut servir :) Le fait que le code ne fasse rien me fait penser que c’est peut être purement pour satisfaire ou aider le système de type dans un truc avec un haut niveau de généricité.

      Par curiosité, @l’auteur, c’est quoi le contexte ?

      • [^] # Re: Lisibilité

        Posté par  (site web personnel) . Évalué à 8.

        C'est pour supporter l'argument axis de numpy.argmin ou numpy.argmax dans pythran. Le commit complet, à peu près autour de la bonne ligne, est là :

        https://github.com/serge-sans-paille/pythran/pull/803/files#diff-b20072554ff33a1cd786318f40ff0368R110
        (attention, réponse demandant un peu de connaissance de l'API numpy)

        Comme le paramètre d'entrée est une expression-tableau de dimension N (paramètre template), et qu'on veut calculer argmin en suivant l'axe axis (paramètre non connu à la compilation), il faut générer les N patrons de boucle possibles suivant la valeur de l'axe.

        Petit exemple pour N = 3, on peut vouloir générer (cas du min qui est un peu plus simple, mais c'est l'idée)

        // axis == 2
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; ++j)
                for(int k = 0; k < p; ++k)
                   out[j, k] = min(out[j, k], in[i, j, k]);
        
        // axis == 1
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; ++j)
                for(int k = 0; k < p; ++k)
                   out[i, k] = min(out[i, k], in[i, j, k]);
        
        // axis == 0
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; ++j)
                for(int k = 0; k < p; ++k)
                   out[i, j] = min(out[i, j], in[i, j, k]);

        le code pointé plus haut génère ces différentes boucles quelque soit N, dans le cas de argmin, en gardant un typage correct.

        Je ne sais pas si je suis clair :-/

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.