C++17 branche à la compilation (`if constexpr`)

Posté par  . Édité par Oliver, Davy Defaud, Benoît Sibaud et claudex. Modéré par Benoît Sibaud. Licence CC By‑SA.
Étiquettes :
28
5
déc.
2016
C et C++

Chaque jour de décembre a droit à sa surprise. Après la fixation de l’ordre d’évaluation des expressions, aujourd’hui, le calendrier de l’Avent du C++ présente la spécification technique P0292 concernant les conditions à la compilation, grâce à if constexpr.

Logo C++FRUG représenté par un gros "C++" au centre du cercle de la Francophonie

Sommaire

Plusieurs nommages

Cette fonctionnalité a connu plusieurs nommages au fil des discussions entre les membres du comité de standardisation :

Chronologie du nommage de la fonctionnalité

  • 2011 : le nommage original était static_if et static_else ;
  • 2015 : l’avènement du mot‐clef constexpr apporte une nouvelle terminologie qui se différencie de static. Le nommage devient constexpr_if et constexpr_else ;
  • mars 2016 : lors de la première réunion du comité de normalisation, les deux mots ont été détachés pour donner constexpr if, et constexpr_else devient juste else.
  • juin 2016 : à la seconde réunion, les deux mots sont inversés pour donner if constexpr.

Simplification du code générique

Cette fonctionnalité est un outil puissant pour écrire du code générique compact. On combine plusieurs patrons (templates).

Sans if constexpr :

void fonction()
{ std::cout << std::endl; }

template <class T>
void fonction (const T& t)
{ std::cout << t; }

template <class T, class... R>
void fonction (const T& t, const R&... r)
{
  fonction(t);    // Gère un argument
  fonction(r...); // Gère le reste
}

Avec if constexpr :

template <class T, class... R>
void fonction (const T& t, const R&... r)
{
  std::cout << t;    // Gère un argument
  if constexpr (sizeof...(r))
    fonction(r...);  // Gère le reste
  else
    std::cout << std::endl;
}

Remplacement du SFINAE

Brève explication sur le SFINAE

D’après Wikipédia en français :

Le mécanisme décrit par l’abréviation SFINAE (Substitution Failure Is Not an Error) permet de surcharger un template par plusieurs classes (ou fonctions), même si certaines spécialisations, par exemple, ne peuvent pas être utilisées pour tous les paramètres de templates. Le compilateur, lors de la substitution, ignore alors les instanciations inapplicables, au lieu d’émettre une erreur de compilation.

C’est une technique de métaprogrammation qui permet de sélectionner une fonction générique surchargée à la compilation. Plus spécifiquement, le SFINAE signifie que le compilateur ne considère pas comme une erreur un problème d’instanciation. Le compilateur va alors essayer de trouver une autre instanciation similaire possible.

Voir aussi l’article sur Wikipédia en anglais ou sur cppreference.com. Un article en français, mais non libre, est également disponible sur developpez.com.

Chère lectrice, cher lecteur LinuxFr.org. Souhaite‐tu une dépêche consacrée au SFINAE ? Alors exprime‐toi dans les commentaires et de nombreuses personnes vont certainement unir leurs forces pour t’offrir un superbe article sous licence libre. Bon, si tu n’oses pas demander, personne n’aura l’impulsion pour se lancer…

Revenons au if constexpr

Dans certains cas, le if constexpr peut avantageusement remplacer la technique du SFINAE.

Sans if constexpr

Voir sur gcc.godbolt.org.

template<class T>
auto f(T x) -> decltype(std::enable_if_t<   std::is_function_v<decltype(T::f)>,int>{})
{                                      // ^---true si T::f existe et que c'est une fonction
  return x.f();
}

template<class T>
auto f(T x) -> decltype(std::enable_if_t< ! std::is_function_v<decltype(T::f)>,int>{})
{                                      // ^---le contraire
  return 0;
}

Trait

L’exemple précédent utilise enable_if et is_function qui sont des traits de la bibliothèque standard. Ce sont des classes templates qui réalisent un petit traitement à la compilation nécessaire au SFINAE.

Par simplification, nous avons utilisé les suffixes …_t et …_v dans std::enable_if_t (C++14) et std::is_function_v (C++17) qui correspondent respectivement au type d’aide std::enable_if<…>::type et à la variable d’aide std::is_function<…>::value.

La bibliothèque standard de GCC 7 implémente enfin la variable d’aide …_v (C++17). En revanche, cela ne semble pas encore être le cas pour Clang-3.8.

Avec if constexpr

Voir sur gcc.godbolt.org.

template<class T>
int f (T x)
{
  if constexpr( std::is_function_v<decltype(T::f)> )
    return x.f();
  else
    return 0;
}

Mixer if constexpr et if classique

Il est possible de mixer les deux syntaxes. La convention actuelle est de commencer par if constexpr. L’inférence du type de retour peut aussi être utilisée. Un exemple vaut mieux qu’un long discours :

template <bool B>
auto f (std::string const & s)
{
  if constexpr (B)
    return std::string("top");
  else if (s.size() > 42)
    return true;
  else
    return false;
}

Notons que la fonction f() n’a pas besoin d’être constexpr pour utiliser if constexpr, tout comme pour utiliser static_assert(). Même les lambdas peuvent utiliser cette fonctionnalité, que du bonheur.   \o/

Remplacement du #if ?

if constexpr peut, dans certains cas, remplacer le #if du préprocesseur, mais ce n’est pas l’objectif. Après, selon l’usage qui en sera fait…

À ce propos, qui veut se lancer dans des expérimentations ? Merci de publier vos trouvailles dans les commentaires. ;-)

Faut‐il continuer à apprendre le C++ ?

Panneau « Please Do Not Feed the Trolls » Panneau Troll barré
Ne pas nourrir les trolls Ne pas nourrir les trolls

Merci de nous aider à structurer et consolider les différentes idées sur cette question dans l’espace de rédaction collaboratif de LinuxFr.org : Faut‐il continuer à apprendre le C++ ?

Réutilisation

Le texte de cette dépêche est protégé par le droit d’auteur la gauche d’auteur et réutilisable sous licence CC BY-SA 4.0. Les images utilisées sont aussi sous licence libre (cliquer sur l’image pour plus de détails).

Donc, n’hésitez pas à réutiliser ce contenu libre pour créer, par exemple, des supports de formation, des présentations (Meetups), des publications sur d’autres blogs, des articles pour des magazines, et aussi un article C++17 sur Wikipédia dès que Wikipédia passera de la licence CC BY-SA 3.0 à la CC BY-SA 4.0 (le contenu de cette dépêche utilise la version la CC BY-SA 4.0).

Les auteurs

Par respect de la licence, merci de créditer les auteurs :

Continuer à améliorer ce document

Malgré tout le soin apporté, il reste certainement des oublis, des ambiguïtés, des fôtes… Bien que cette dépêche restera figée sur le site LinuxFr.org, il est possible de continuer à l’enrichir sur le dépôt Git du Groupe des utilisateurs C++ francophone (C++FRUG). C’est donc sur ce dépôt que se trouvent les versions les plus à jour.   (ღ˘⌣˘ღ)

Alors que cet article restera figé sur le site LinuxFr.org, il continuera d’évoluer sur le dépôt Git. Merci de nous aider [à maintenir ce document à jour][md] avec vos questions/suggestions/corrections. L’idée est de partager ce contenu libre et de créer/enrichir des articles Wikipédia quand la licence sera CC BY-SA 4.0.   ٩(•‿•)۶

La suite

La dépêche suivante nous dévoilera une autre nouveauté du C++17.

Chère lectrice, cher lecteur LinuxFr.org. Tu souhaites apporter ta pierre à cet édifice ? Rejoins‐nous dans l’espace de rédaction collaborative sur LinuxFr.org (un compte est nécessaire pour y accéder).

À suivre…

Aller plus loin

  • # Nouveau langage

    Posté par  . Évalué à 10.

    D'abord, merci pour cette série d'articles, c'est assez agréable de pouvoir passer 5 minutes à lire quelque chose d'assez compact sur chaque nouveauté plutôt qu'une énorme dépêche ultra-résumée.

    Sur le fond, quand je lis le code proposé, je me dis quand même qu'on est carrément en train de définir un nouveau langage avec la programmation générique. Je n'ai pas l'intention de résoudre en un commentaire la discussion éternelle sur le fait que C++ est ou n'est pas un langage orienté objet, mais mon point de vue est quand même que le multi-paradigme a une limite, c'est celle de la compréhension croisée entre les différents programmeurs.

    Concrètement, j'ai un mal fou à discuter avec les «petits jeunes» qui apprennent la syntaxe générique du C++, parce que j'ai quand même l'impression que pour beaucoup d'applications, la programmation OO et la programmation générique permettent de faire des choses qui se superposent largement, mais qui sont assez incompatibles. On peut partir sur un design à base de templates ou sur un design à base de hiérarchies de classes, et une fois ce choix fait, on a quasiment deux dialectes incompatibles. Ça va devenir de plus en plus compliqué de définir ce qu'est le C++, puisqu'au final quelque chose comme "le logiciel est codé en C++" ne voudra plus dire grand chose, à part qu'il est compilable par un compilateur C++.

    • [^] # Re: Nouveau langage

      Posté par  . Évalué à 4.

      Ça va devenir de plus en plus compliqué de définir ce qu'est le C++, puisqu'au final quelque chose comme "le logiciel est codé en C++" ne voudra plus dire grand chose, à part qu'il est compilable par un compilateur C++.
      

      Si je me fie à la précédente dépêche sur l'ordre d'évaluation des prédicats. L'objectif est de passer de "le logiciel est codé en C++ => c'est de la POO." à "le logiciel est codé en C++ => il est compilable par un compilateur C++ et son comportement sera le même et prévisible quelques soit le compilateur utilisé."

      Ce qui me semble une bonne idée.

    • [^] # Re: Nouveau langage

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

      Oui, tout à fait.

      Au début (il y a une quarantaines d'années), le C++ s’orientait exclusivement vers la Programmation Orientée Objet (POO).

      Mais, depuis C++11, le C++ évolue dans d'autres directions :

      • Faciliter la programmation générique (avec les variadique par exemple) ;
      • Faciliter la méta-programmation (avec static_assert, constexpr…) ;
      • Et se passer aussi de la POO quand c'est possible, exemples :

        a.begin() ->  std::begin(a)
        a.size()  ->  std::size(a)
        

      Si nous regardons les nouveaux langages comme Go ou Rust, nous voyons bien que la POO n'est plus à la mode.

      Je pense qu'en 2017 nous publierons de petites dépêches pour expliquer les aspects de la programmation générique et surtout la méta-programmation (et un peu de programmation fonctionnelle) car c'est l'intérêt de coder en C++. Le mot-clé template fait peur, démystifions-le ;-)

      Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

      • [^] # Re: Nouveau langage

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

        Parenthèse: A.Stepanov se défend que la STL historique ait été conçue OO. Il y a diverses vidéos sur le sujet.

        Dans le cas de v.begin() il sous-entend dans son Notes on Programming que pour passer le véto de l'intelligentzia OO qui n'avait pas encore migré vers d'autres langages à l'époque (le subjectif dans le phrasé est mien), il avait dû concéder à faire de size & cie des fonctions membres et non des fonctions libres. Depuis le C++11 on corrige le tir. Il faut attendre C++17 pour avoir std::size()—même s'il est facile à écrire dès le C++11: c'est un oubli.

        A noter aussi les propositions autour du Uniform Call Syntax par Stroustrup et d'autres qui n'ont pas été retenues pour le C++17—j'avais donné le lien dans les commentaires d'une précédente dépêche vendredi.

        • [^] # Re: Nouveau langage

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

          Quel est le problème avec les fonctions membres, ou plutôt, pourquoi les fonctions libres seraient-elles mieux ?

          • [^] # Re: Nouveau langage

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

            Par exemple, la fonction suivante ne fonctionne que pour les structures (classes) fournissant des fonctions membres recupere_x() et recupere_y().

            template <typename T>
            auto calcule (T objet)
            {
              auto x = objet.recupere_x();
              auto y = objet.recupere_y();
              return (x+x) - (y*y);
            }

            Supposons que nous devions utiliser deux bibliothèques développées par deux entités différentes :

            • Un bibliothèque fournit une structure (classe) qui ne possède pas les fonctions membres recupere_x() et recupere_y() ;
            • La seconde bibliothèque fournit la fonction calcule.

            On n'a pas la possibilité de modifier aucune des deux bibliothèques.
            Comment faire pour passer la structure (classe) à la fonction calcule ?


            Voyons maintenant, une implémentation de la fonction calcule utilisant cette fois-ci les fonctions libres :

            template <typename T>
            auto calcule (T objet)
            {
              auto x = recupere_x(objet);
              auto y = recupere_y(objet);
              return (x+x) - (y*y);
            }

            Et c'est beaucoup plus facile de passer la structure (classe) à la fonction calcule(). Deux possibilités :

            • les fonctions libres recupere_x() et recupere_y() sont déjà disponibles dans une bibliothèque ;
            • ou bien nous devons les implémenter, ce qui est bien plus facile que l'exercice précédent.

            C'est dans ce second paradigme que sont conçus beaucoup de langages récents comme Rust et Go.
            Cela permet naturellement de respecter le principe ouvert/fermé.


            Prenons un exemple dans un vieux langage… allez prenons Python qui doit avoir une trentaine d'années, et étudions sa fonction libre len() :

            str = 'test'
            r = len(str)  # retourne 4

            Certains objets implémentent la fonction membre __len__() mais la convention est de passer par la fonction libre len().

            Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

            • [^] # Re: Nouveau langage

              Posté par  . Évalué à 2.

              Comment faire pour passer la structure (classe) à la fonction calcule ?

              Tu as oublié une autre solution: tu crée un adaptateur, ce qui grosso modo revient à implémenter des fonctions non-libres recupere_x() et recupere_y() (c'est pas bien plus compliqué qu'implémenter des fonctions libres).

              C'est la création de la classe adaptateur qui vous gêne?

              • [^] # Re: Nouveau langage

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

                Oui, tu as raison, une bonne façon de faire est de créer un Adaptateur. Mais le sujet de mon commentaire était de surligner l'intérêt des fonctions libres qui permettent d'étendre un objet de l'extérieur. Donc, j'avais volontairement omis les autres possibilités qui nécessitent plus de code, plus de relecture, plus de test, plus de maintenance… ;-) (bon oui, je l'accorde pas tellement plus)

                Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

                • [^] # Re: Nouveau langage

                  Posté par  . Évalué à 1.

                  Je commence à détecter un certain trait chez les développeurs C++: l'optimisite proactive aiguë!

      • [^] # Re: Nouveau langage

        Posté par  . Évalué à 4.

        Je pense qu'en 2017 nous publierons de petites dépêches pour expliquer les aspects de la programmation générique et surtout la méta-programmation (et un peu de programmation fonctionnelle) car c'est l'intérêt de coder en C++.

        Bah, je n'ai rien personnellement contre la programmation générique. C'est juste que le C++ OO et le C++ générique ressemblent de plus en plus à deux dialectes distincts, au moins autant que le C et le C++98. Et s'il est techniquement possible de mélanger les deux (le compilateur le comprend), c'est de plus en plus compliqué de les faire cohabiter dans le même projet. Mon impression est que de toutes manières les programmeurs ne vont plus pouvoir s'en sortir avec les deux paradigmes à la fois; d'un côté on aura le monde des design patterns et du typage dynamique, et de l'autre côté on aura les templates de templates et le typage statique.

        Par contre, du coup, appeler tout ça "C++" et continuer à prétendre que c'est le même langage, ça n'est pas très sain. Pour recruter quelqu'un dans une boîte ou dans un projet, il va falloir préciser quels aspects du C++ on souhaite privilégier. De même, il pourrait être sympa de commencer à définir des sous-ensembles plus ou moins officiels qu'on pourrait passer aux compilateurs, pour vérifier qu'on respecte bien le cadre imposé. J'ai appris il y a peu de temps que pour émuler ce comportement, certains n'acceptaient que d'anciennes versions des compilateurs, ce qui me semble profondément stupide (on peut ne pas vouloir autre chose que du C++98 tout en voulant profiter des corrections de bugs et des optimisations des compilos récents).

        Le mot-clé template fait peur, démystifions-le ;-)

        Je ne pense pas que ça soit les templates qui fassent peur, c'est l'ensemble pas très intuitif des syntaxes associées.

        • [^] # Re: Nouveau langage

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

          Ces paradigmes ne doivent pas être opposés. Ils offrent des choses complémentaires. Si je dois gérer mes observateurs à la main, je vais utiliser les classes génériques standards. Ne pas le faire serait totalement idiot de ma part.

          Pareil, pour gérer la mémoire, par défaut je vais utiliser des unique_ptr<> qui sont génériques. Et je les mettrai dans des objets.

          Je comprends que devoir écrire ce genre de classe from scratch fasse peur à quelqu'un qui n'est pas habitué à la lourdeur de la syntaxe et à qui l'école n'a appris qu'un bout de l'OO (pour factoriser des données de bases de données et non pas pour factoriser des comportements) et du procédural. Mais pour la majorité d'entre nous, on n'a rien de tel à écrire. Juste à utiliser.

          Reste que ce ne sont pas des paradigmes opposés.

          Je dirai aussi, que le mélange est tout sauf récent. Cf la Thèse de Jim O Coplien qu'il a ultérieurement édité en livre: Multiparadigm Design in C++—cf la FAQ de dvpz pour les liens. Un des aspects les plus intéressants, c'est toute la partie sur la dualité commonalities et variability points. C'est le B à ba. On a des zones de code communes, et dedans, il y a des points où des détails vont varier. Et ça on sait le faire depuis longtemps: socket win32 ou socket POSIX ? Tableau d'entiers ou tableau de doubles ? Affichage de carrés, cercles, polygones ou autre figure géométrique ?

          Toute la question est la dynamicité derrière les points de variations—et leur potentielle capacité à évoluer sans requérir de modification. Est-ce quelque chose que l'on sait déterminer dans le code source ? En fonction de la plateforme de build ? Est-ce lié à des plugins (choix du .so à charger)? Est-ce totalement dynamique ? Et bien en fonction de ça, ou va jouer avec des #if, on va compiler un .c ou un autre, on va avoir des instances d'un type ou d'un autre, ou on va choisir le paramètre template d'un type générique.

          Bref. C'est juste un autre levier qui offre plus de finesse. Et dans un même projet, il peut être intéressant de disposer de plusieurs façons pour procéder.

          • [^] # Re: Nouveau langage

            Posté par  . Évalué à 10.

            Ces paradigmes ne doivent pas être opposés. Ils offrent des choses complémentaires.

            Et du coup, il faut combien de décennies d'apprentissage avant de savoir choisir le bon paradigme?

            pour gérer la mémoire, par défaut je vais utiliser des unique_ptr<> qui sont génériques.

            Je pense que personne ne considère qu'utiliser les conteneurs de la STL revient à faire de la programmation générique.

            Je comprends que devoir écrire ce genre de classe from scratch fasse peur à quelqu'un qui n'est pas habitué

            Je n'aime pas le fenouil braisé. Est-ce que ça veut dire que j'ai peur du fenouil braisé? Est-ce légitime de m'expliquer que la seule raison pour laquelle je n'aime pas le fenouil braisé est que je n'y suis pas habitué? Au bout d'un moment, c'est agaçant.

            Reste que ce ne sont pas des paradigmes opposés.

            Si, parce qu'ils sont incompatibles, et qu'ils répondent tous les deux au même besoin de base (utiliser le même code pour appliquer les mêmes opérations à des objets différents qui partagent un certain nombre de propriétés). Si on va au-delà des simples conteneurs style STL, on se retrouve avec des possibilités d'architectures parallèles (d'un côté, Truc<A> et Truc<B>, d'un autre côté TrucA et TrucB qui dérivent de Truc), avec chacun leurs paradigmes, leur syntaxe, leurs contraintes et leurs avantages. Mélanger les deux dans un projet un peu complexe me semble assez incongru, au moins autant que de mélanger des paradigmes C et C++, par exemple.

            • [^] # Re: Nouveau langage

              Posté par  . Évalué à 2.

              C'est pas bien de ramener les gens à la réalité. :-p

              "Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)

            • [^] # Re: Nouveau langage

              Posté par  (site web personnel) . Évalué à 6. Dernière modification le 06 décembre 2016 à 11:51.

              Ces paradigmes ne doivent pas être opposés. Ils offrent des choses complémentaires.

              Et du coup, il faut combien de décennies d'apprentissage avant de savoir choisir le bon paradigme?

              Combien de décennies faut-il pour savoir correctement concevoir? Je ne parle même pas de programmer, mais d'assimiler l'OO avec ses bonnes pratiques, le génie logiciel, la gestion des ressources, etc. Tout demande un investissement dans nos métiers—je sais que ce n'est pas ce que nos employeurs voudraient entendre.

              Et pour savoir quoi choisir, la question, mes collègues savent toujours y répondre: est-ce un choix qui se fait à la compilation ? A l'exécution ? Au lancement ?

              Après, j’admets volontiers que les choses deviennent compliquées quand on veut le beurre et l'argent du beurre. Exemple typique : un calcul sera appliqué sur des pixels d'images du spatial (en 10000x10000), mais la nature du calcul on ne la connait pas avant que l'utilisateur ne lance l'application. Là on veut un point de variation dynamique, et éviter de payer pour ce choix à l'exécution.

              Je pense que personne ne considère qu'utiliser les conteneurs de la STL revient à faire de la programmation générique.

              En termes de paradigmes, certains distinguent la programmation générique où l'on va utiliser, voire écrire des types génériques (comme on faisait en Ada sans que cela ne choque personne), et la métaprogrammation template où l'on va commencer à adapter automatiquement l'algorithme générique choisi en fonction des types que l'on manipule (p.ex. un tri sur une collection qui offre un accès direct et celui sur une collection qui n'offre qu'un accès séquentiel ne sera pas le même ; std::copy sur des POD pourra utiliser memcpy, etc).

              Les trucs les plus avancés que j'ai fait dans du code métier, c'est définir des listes de types décrivant des informations pouvant être extraites d'une trame binaire (ordre, nb bits dans la trame, type C++ associé après décodage). J'ai fait ça par métaprog. D'autres décrivent les trames en XML et font des parseurs compliqués, ou des générateurs de code. Dans tous les cas, un investissement est requis pour la technique employée.

              Pour le fenouil braisé, on a des mécanismes similaires : l'envie de creuser, le temps, l’honnêteté d'admettre que c'est adapté (ou non!!) à une situation. Il y a beaucoup de critères. L'essentiel de ces critères est humains. Je renvoie à la première partie de la dernière présentation de Dan Saks au CppCon2016. Çà fait bien 20ans (plus?) qu'il essaie de vendre le typage supérieur à 0 surcoût du C++ avec template à la communauté C embarqué. Dans la première partie de la vidéo, il revient sur ce qui s'est passé et les raisons (humaines) de son échec.

              Si ceux qui connaissent le C++ sont convaincus que la généricité peut s'importer facilement, voire plus facilement qu'un conception OO de qualité (combien ont compris SOLID et en particulier le LSP, et ce qu'est véritablement l'encapsulation? Trop peu.), pourtant ça bloque.

              Tu me dis que ce n'est pas parce que ça (te) fait peur. Pourquoi alors ?

              PS: Truc<T> peut tout à fait hériter de ITruc ou de TrucImpl. C'est même assez classique quand on veut combiner une variabilité dynamique avec une statique.

              • [^] # Re: Nouveau langage

                Posté par  . Évalué à 3. Dernière modification le 07 décembre 2016 à 14:45.

                PS: Truc peut tout à fait hériter de ITruc ou de TrucImpl. C'est même assez classique quand on veut combiner une variabilité dynamique avec une statique.

                C'est super bancal comme solution, parce que ça demande une surcouche, et que l'interface n'est pas templatée. Prenins un exemple trivial : je veux faire un vecteur de vecteurs de différents types. On peut bien créer quelque chose comme vector<MyVec*> avec des MyvecT<T> qui dérivent d'une classe MyVec, mais à ma connaissance on ne peut pas avoir une méthode MyVec::at(size_t i) const qui retourne le bon type (*).

                Si la programmation générique était fongible avec la POO, alors le langage devrait assimiler un template à une interface, et autoriser des choses comme vector * v; qui se comporteraient comme des pointeurs vers des classes virtuelles pures. Je ne sais pas si c'est pour des raisons techniques ou philosophiques que le C++ a choisi des syntaxes incompatibles, mais je ne vois pas comment on peut l'interpréter autrement que par la volonté de définir deux dialectes qui ne sont pas destinés à cohabiter dans le même code.

                (*) Enfin si, je crois qu'on peut si la méthode retourne un void* qu'on peut caster avec un typename qu'on aurait défini dans Myvec<T>. Je ne sais pas s'il existe une personne sur terre qui pourrait croire que c'est une bonne idée.

                • [^] # Re: Nouveau langage

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

                  OK, je pense voir ce que tu veux dire.

                  Tu es sur l'interface d'utilisation. J'étais sur POO pour l'interface et templates (qui ne doivent pas fuir au niveau de l'interface) pour l'implémentation—ce qui n'a rien de bancal. Effectivement, le C++ n'offre pas un polymorphisme paramétrique dynamique. En C++, le duck typing c'est 100% à la compilation.

                  Il y a feintes pour essayer de combiner tout ce beau monde. Cf les articles qui parlent de type erasure en C++—pas sûr que le même sens soit donné dans d'autres communautés.

                  Bref, il y a des cas de combinaison possibles sur une même famille de types. Et ça marche très bien. Le truc, c'est que dans ce cas, je ne veux pas d'un at() qui renvoie un template—enfin je ne veux jamais de at() étant peu réception à la programmation défensive, mais c'est une autre histoire, je sais ne pas pouvoir vouloir d'un T ITruc::operator[]() non plus—oui, de connaitre la limitation, ça oriente fortement ce que je m'autorise à vouloir faire.

                  NB: l'équivalent template de l'interface, c'est le concept.

                  Pour les raisons, je pense qu'il faille aller chercher dans le technique. En C++, les template s'appliquent sur des scalaires comme des objets, et c'est assez contraignant. Je ne sais pas comment font C# et D sur cet aspect.

                  • [^] # Re: Nouveau langage

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

                    s/peu réception/peu réceptif—il y a en d'autres, mais là ça ne veut vraiment plus rien dire…

                  • [^] # Re: Nouveau langage

                    Posté par  . Évalué à 3.

                    je sais ne pas pouvoir vouloir d'un T ITruc::operator non plus—oui, de connaitre la limitation, ça oriente fortement ce que je m'autorise à vouloir faire.

                    Oui, donc dans les faits si tu ne veux pas utiliser le polymorphisme, bah la programmation à base de templates ne te pose pas de problème :-) Encore une fois, mon point de vue n'est pas de dire que la programmation générique c'est pourri, c'est de dire que le C++ OO et le C++ générique (au sens de templates non triviaux sur des objets complexes) sont philosophiquement et techniquement incompatibles. On peut concevoir tout un projet en C++ OO ou en C++ générique, mais ça va fortement conditionner le sous-ensemble du C++ et les designs qu'on va utiliser.

                    Bien sûr, on peut écrire des trucs hybrides, ça va compiler, et ça va faire d'excellents exercices pour les gens qui aiment se prendre la tête. La question, c'est plutôt "est-ce que des projets réels vont utiliser ça"?

                    • [^] # Re: Nouveau langage

                      Posté par  . Évalué à 3.

                      Ce n’est pas une question de langage, c’est aussi une question que certaines choses ne sont pas une bonne idée. Un grand classique, c’est le coup de si B dérive de A, alors vector dérive de vector qui est en réalité une très mauvaise idée (violation flagrante du LSP).

                      La question, c'est plutôt "est-ce que des projets réels vont utiliser ça"?

                      Ça m’est arrivé. Personnellement, je ne vois pas ça comme des incompatibilités, mais des complémentarités. Après, quand tu commences à voir les bénéfices du polymorphisme statique, tu as tendance à ne vouloir utiliser plus que ça :).

                      Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

                    • [^] # Re: Nouveau langage

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

                      Les projets réels les utilisent tout le temps.
                      - Je veux un tableau dans une classe concrète? std::vector<>. C'est générique et je m'en sers.
                      - Je veux un truc qui va collecter automatiquement la mémoire ou n'importe quoi d'autre ? std::unique_ptr<>.
                      - Je veux trier un tableau ? std::sort()
                      - Je veux des images avec des types de pixel générique ? Ca marche aussi.

                      On ne va pas faire 100% dans un style et 100% dans un autre. On va prendre le style qui va bien pour un besoin donné. De la même façon que l'on ne va pas dériver pour le plaisir. On compose bien souvent à la place car cela offre plus de souplesse en ouvrant la porte au pattern Stratégie.

                      PS: Dans le monde des typeurs de canards cela que l'on nomme juste "polymorphisme" désigne le "polymorphisme paramétrique" qui en C++ correspond aux templates, et qui est statique (en C++).

                      • [^] # Re: Nouveau langage

                        Posté par  . Évalué à 3.

                        Je veux un tableau dans une classe concrète? std::vector<>. C'est générique et je m'en sers.

                        J'ai l'impression qu'on ne se comprend pas, et au bout d'un moment ça tourne en rond. Je pense que personne ici ne parle de ne pas utiliser les conteneurs de la STL ; utiliser la STL n'a pas grand chose à voir avec la programmation générique.

                        La question (telle que je la comprend), c'est de choisir entre

                        struct EtatCivil {
                           string nom;
                           string prenom;
                        };

                        et

                        template<typename n, typename p>
                        struct EtatCivil {
                            n nom;
                            p prenom;
                        };

                        comme paradigme de base. Sans aller jusqu'à prétendre qu'il faut 100% de l'un ou de l'autre, mon argument c'est que la plupart des problèmes ont une solution avec l'un ou l'autre, et que comme les deux sont très difficilement fongibles, on va orienter l'ensemble de l'architecture du programme dans un sens.

                        Je veux trier un tableau ? std::sort()

                        Je pense que c'est un excellent exemple. C'est générique? OK. Mais l'ergonomie est dégueulasse, parce que tu as oublié que ça s'utilisait comme ça : std::sort(v.begin(), v.end());. Évidemment, à force de voir ça, on est habitué, mais tu ne trouves pas que v.sort() serait bien plus élégant? Il permettrait aussi le polymorphisme si tous les containers dérivaient d'une interface STL_cont. Bref, la programmation générique pour la bibliothèque standard, c'est un choix technique, mais ça me semble assez excessif de prétendre que c'est pratique et ergonomique ; d'une manière générale, la STL est relativement imbittable, les syntaxes sont contre-intuitives, et le code est assez illisible.

                        • [^] # Re: Nouveau langage

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

                          Utiliser la SL a tout à voir avec la programmation générique. Avant de disposer d'un std::vector<>, il faut l'écrire, et après libre à l'utilisateur de s'en servir. Convergeons vers ton exemple (hors STL mais dans la SL) il y a std::basic_string<> ou même std::basic_ostream<>. Très peu ont conscience que ces trucs existent et pourtant ils s'en servent au travers des typedefs std::string, std::ostream, ou carrément via la variable globale std::cout.

                          Ces types sont génériques. Si les chaines ne piochent dans le paradigme OO que l'abstraction et l'encapsulation, les flux sont quant à eux plein d'héritage, multiple de surcroit, de points de variations dynamiques (ou comment filtrer à la volée un flux ou l'altérer pour justifier un texte p.ex.), de design pattern Stratégie, et je peux en oublier. Et ? Ils sont totalement génériques sur le type de caractères, et sur les descriptifs des caractères.

                          Nous sommes du côté d'une bibliothèque et pas d'un code métier. Et c'est probablement ça qui fait toute la différence. Côté métier offrir de la généricité est moins souvent pertinent. De fait une classe EtatCivil n'a pas grand intérêt à être générique. Côté bibliothèque j'ai pu travailler avec ITK et OTB, là on a des types images dans une hiérarchie avec pourtant un paramètre template pour le type de pixel. On a aussi des hiérarchies encore plus touffues pour dire qu'un truc assimilable à une image peut être une image, un fichier ou une transformation qui va servir à calculer une nouvelle image. Pour le coup ITK utilise des hiérarchies là où la STL (+ std::string) est 100% orientée concepts.

                          Concernant sort, là tu bloques sur l'écriture OO mainstream on dirait. Parenthèse OO: Personnellement, je constate ici l'échec de Java qui comme le C++ a des types primitifs, mais qui a introduit un Top Type (type dont tout objet dérive). Résultat le code de divers algorithmes comme sort est dupliqué entre les tableaux primitifs et les collections à accès direct. Python n'a pas ce problème. Il y a un top type chez lui, mais pas de type primitif.

                          Côté écriture de sort, la STL v2 est en cours de préparation. Le C++ assume totalement que la généricité est plus adaptée que des hiérarchies compliquées pour des algorithmes. L'écriture qui est sympa c'est sort(v) pourquoi est-ce que cela devrait être v.sort()? Avec CLOS, la syntaxe d'appel est (func x y z). Ce qui rend la gestion du dispatch multiple tout ce qu'il y a de plus naturel. Une petite discussion à ce sujet dans un article que j'avais bien aimé (le lien pointe directement sur le paragraphe qui discute s'il faut écrire kick the dog ou dog the kick).

                          Pour en revenir à la STL v2, en plus de l'écriture action::unique(action::sort(vi));, elle va supporter l'écriture vi |= action::sort | action::unique; qui marcheront aussi sur des tableaux natifs ou sur n'importe quel conteneur à accès direct (seulement? je n'ai pas vérifié) qui expose begin() et end() en libre ou en membre.

                          Pour la complexité de la STL, je dirai que pour un bonne part c'est une question d'habitude. Pour avoir lu et écouté Stepanov, ce à quoi il a abouti est naturel. Comme en maths, il voit que chaque algo a des prérequis pour être appliqué, à partir de là, hormis les template <typename NomdeConcept1, ... en début de signature, le reste est franchement clair. Après pour le code des implémentations de la SL, il y a la quasi obligation de tout préfixer avec des tirets-bas et la volonté d'auto-adaptativité à la nature des choses manipulées histoire d'optimiser (nous sommes en C++ après tout), ce qui ne rend pas la lecture toujours triviale. Plus des hacks pour éviter de produire des messages d'erreurs trop inintelligibles, plus de la programmation par contrat. Mais est-ce que cela nous concerne vraiment ?

                          Je rajouterai aussi pour avoir essayé de jouer à ça dans ma jeunesse que le paradigme OO (tel qu'implémenté en Pascal/Delphi, C++ et Java) est totalement incapable de modéliser correctement les notions de groupes, anneaux et cie quant on commence à vouloir introduire du dispatch multiple dans des fonctions.

                          • [^] # Re: Nouveau langage

                            Posté par  . Évalué à 4.

                            Je rajouterai aussi pour avoir essayé de jouer à ça dans ma jeunesse que le paradigme OO (tel qu'implémenté en Pascal/Delphi, C++ et Java) est totalement incapable de modéliser correctement les notions de groupes, anneaux et cie quant on commence à vouloir introduire du dispatch multiple dans des fonctions.

                            J'ai tendance à penser que cette limitation n'est pas propre aux différentes implémentations de la POO que tu cites, mais est inhérente au paradigme lui-même : la POO, qui met en avant la notion d'abstraction, c'est de l'abstraction vue par un enfant de quinze ans et à un moment, ça coince.

                            Dans un de ses commentaire, Olivier H disait : « Le mot-clé template fait peur, démystifions-le ;-) » : je suis impatient de lire une de vos dépêches spécifique à la description, au fonctionnement et à l'usage des templates.

                            Juste une question : j'ai lu que le langage des template était Turing-complet, pourquoi lui avoir conféré cette propriété ? N'est-ce pas plutôt un défaut ?

                            Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                            • [^] # Re: Nouveau langage

                              Posté par  (site web personnel, Mastodon) . Évalué à 5.

                              C'était surtout un accident, au début :) Quelqu'un s'est dit: "hé, mais on peut faire un langage Turing complet avec ça!". Et ensuite y'a eu des gens pour s'en servir…

                            • [^] # Re: Nouveau langage

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

                              J'ai tendance à penser que cette limitation n'est pas propre aux différentes implémentations de la POO que tu cites, mais est inhérente au paradigme lui-même : la POO, qui met en avant la notion d'abstraction, c'est de l'abstraction vue par un enfant de quinze ans et à un moment, ça coince.

                              J'ai aussi tendance à avoir cet avis, mais dans le doute (car je suis loin de connaitre les langage OO qui flirtent de très près avec le monde fonctionnel) j'ai préféré spécifier un contexte.

                              • [^] # Re: Nouveau langage

                                Posté par  . Évalué à 5.

                                Je suis loin, mais alors très loin d'être un fin connaisseurs des langages de programmations existants, alors je me place surtout au niveau de l'approche paradigmatique.

                                Par exemple, en OCaml, il y a bien des objets mais la documentation officielle précise explicitement :

                                This chapter gives an overview of the object-oriented features of OCaml. Note that the relation between object, class and type in OCaml is very different from that in mainstream object-oriented languages like Java or C++, so that you should not assume that similar keywords mean the same thing.

                                et ils sont de fait utilisés en conjonction avec les modules et foncteurs (qui sont ce qui se rapprochent des template du C++ dans ce langage), comme l'illustre le chapitre 5 sur les exemples avancés avec les classes et les modules. Ce qui rejoint ce que tu disais sur la complémentarité de l'OO et des templates en C++.

                                De même, en Haskell, les type classes sont plus à rapprocher des templates que des classes de la POO (voir Demystifying type classes).

                                Là où pêchent inéluctablement la POO, c'est dans l'accès à certains niveaux d'abstraction, comme dans l'exemple proposé par arnaudus. Comme tu lui as répondu, pour du code métier, pouvoir faire de l'abstraction sur les types n'est pas nécessairement pertinent, en revanche pour la conception de bibliothèques, cela est plus que de la coquetterie.

                                Enfin, quand je lis ceci dans un des tes commentaires :

                                NB: l'équivalent template de l'interface, c'est le concept.

                                je me dis que l'on se rapproche bien plus de ce qu'est un concept, qu'avec les exemples de la POO du genre : un chat est un animal, donc il faut implémenter le concept de chat via une classe qui hérite de la classe animal.

                                Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

                                • [^] # Re: Nouveau langage

                                  Posté par  . Évalué à 2.

                                  Oui en gros, c'est la différence entre typage nominal et typage structurel. Chacun a ses avantages et inconvénients. Le typage nominal peut offrir un niveau de sûreté de type très élevé, mais ça se fait au prix de la flexibilité. Le typage structurel est quand a lui extrêmement flexible et il passe partout.

                          • [^] # Re: Nouveau langage

                            Posté par  . Évalué à 2.

                            Concernant sort, là tu bloques sur l'écriture OO mainstream on dirait.

                            Bah non, ma compréhension du problème est que la généricité de la fonction sort() impose l'utilisation d'itérateurs uniquement parce que les containers de la STL n'héritent pas d'une interface commune.

                            L'écriture qui est sympa c'est sort(v) pourquoi est-ce que cela devrait être v.sort()?

                            L'intuition est peut-être la chose au monde la moins partagée, mais pour moi les signatures "intuitives" de ces deux fonctions devraient être

                            std::vector<T> sort(const std::vector<T> &);
                            void vector<t>::sort();

                            Je trouve très surprenant en général que y = f(x) puisse modifier x, et les compilos récents n'ont aucun mal à optimiser v = sort(v) correctement. Ceci dit, j'ai peut-être tendance à tenter de retrouver en C++ des concepts qui viennent des langages de plus haut niveau.

                            elle va supporter l'écriture vi |= action::sort | action::unique;

                            On peut voter quelque part pour le support du Brainfuck dans C++19?

                            • [^] # Re: Nouveau langage

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

                              Je trouve très surprenant en général que y = f(x) puisse modifier x, et les compilos récents n'ont aucun mal à optimiser v = sort(v) correctement. Ceci dit, j'ai peut-être tendance à tenter de retrouver en C++ des concepts qui viennent des langages de plus haut niveau.

                              Là il faudrait faire de la programmation fonctionnelle, effectivement C++ n'est pas le meilleur choix pour ça.

                              elle va supporter l'écriture vi |= action::sort | action::unique;

                              On peut voter quelque part pour le support du Brainfuck dans C++19?

                              Je suppose qu'ils ont voulu faire un truc qui ressemble aux "pipes" en shell unix?

                            • [^] # Re: Nouveau langage

                              Posté par  (site web personnel) . Évalué à 3. Dernière modification le 08 décembre 2016 à 15:35.

                              C'est Alexandrescu, je crois, qui avait pondu un Iterators must go. C'était un des premiers articles de qualité pour pousser vers les remplacement des paires d'itérateurs par des plages (ranges en VO). Entre temps, il est parti vers le D, et je me demande si justement il n'avait pas participé aux ranges en D.

                              Bref, la STL v2 est dans cette mouvance. Et sur ce point, nous sommes d'accord, les algos de la STL v1 ne sont pas des plus ergonomiques. Ils ont de la souplesse, mais ils l'exigent de nous également.

                              C++20 le prochain je pense sinon. Et effectivement, il semblerait plutôt que l'on lorgne du côté des pipe UNIX. Sauf que … au lieu de se contenter d'une monde purement fonctionnel (de variables non modifiables), on traine le boulet OO des états qui sont altérés. D'où le |=. Dans les autres contraintes, il y a les générateurs à la Python. range::ints() et iota() peuvent ainsi générer un nombre (pseudo) infini d'entiers qui seront générés à la volée.

                          • [^] # Re: Nouveau langage

                            Posté par  . Évalué à 2.

                            Concernant sort, là tu bloques sur l'écriture OO mainstream on dirait. Parenthèse OO: Personnellement, je constate ici l'échec de Java qui comme le C++ a des types primitifs, mais qui a introduit un Top Type (type dont tout objet dérive). Résultat le code de divers algorithmes comme sort est dupliqué entre les tableaux primitifs et les collections à accès direct.

                            Justement, la version 10 de Java pourrait introduire les value types et ainsi émuler les templates C++ avec diverses spécialisations de code générique (comme les templates C++)

                            Par exemple, on pourrait enfin écrire ArrayList<int> et il aurait un vrai tableau d'int derrière ça. À comparer avec ArrayList<Integer> qui est un tableau d'objets avec tous plein d'indirections en mémoire.

                            Bon il faudrait déjà que Java 9 finisse par sortir. :-|

                    • [^] # Re: Nouveau langage

                      Posté par  . Évalué à 2.

                      et ça va faire d'excellents exercices pour les gens qui aiment se prendre la tête.

                      C'est pas la définition d'un programmeur c++ ça?

                      Linuxfr, le portail francais du logiciel libre et du neo nazisme.

    • [^] # Re: Nouveau langage

      Posté par  . Évalué à 1.

      Je suis tout à fait d'accord. Concernant la POO, le standard C++17 n'apporte rien de transcendant. Franchement, avoir des fonctionnalités d'introspection en 2017, je ne pense pas que ce soit si superflu (à l'heure où quasiment toute donnée est transmise en JSON). Il n'y a pas une seule bibliothèque C++ aujourd'hui qui sache faire de la sérialisation sans avoir à déclarer chaque champs 2 fois (une fois pour la compilation "statique" et une fois pour le runtime). C'est assez pénible.

      De même, avoir des objets dynamiques, sans se taper des tuple à rallonge qui sont vraiment mauvais niveau praticité et performance et maintenabilité, à part pour la performance, c'est franchement manquant. Alors qu'il suffirait d'ajouter un opérateur pour "membre non trouvé" à gérer au runtime, du genre

        struct MyClass {
            int foo;
            string bar;
            runtime_members __otherMembers; // A bit like a map<any>
      
            template <class T> any operator .(const string & name, const T & arg ) { return __otherMembers[name] = arg; }
        };
      
        MyClass a;
        a.foo = 3;
        a.bar = "hello";
        a.baz = "world"; // call operator .
        a.qux = 4.5;     // ditto
      
      

      Lorsque le compilateur rencontrerait un membre non présent, il appellerait l'opérateur "." (qui peut static_asserter si le nom n'est pas acceptable, ou remplir les champs au runtime.

      • [^] # Re: Nouveau langage

        Posté par  . Évalué à 3.

        Il n'y a pas une seule bibliothèque C++ aujourd'hui qui sache faire de la sérialisation sans avoir à déclarer chaque champs 2 fois (une fois pour la compilation "statique" et une fois pour le runtime). C'est assez pénible.

        Certes, mais en même temps, exposer dans ta sérialisation le nom du membre (côté programme) est vraisemblablement une mauvaise idée. Outre que suivant le type de sérialisation voulue, celui ci peut nécessiter une transformation (par exemple, pour sérialiser vers xml, on aura tendance à transformer les « _ » en « - » ). Donc devoir le définir deux fois mais pas à proprement parler un soucis, en tout cas pas majeur.

        De plus, si les objets sont simplement des conteneurs sans intelligence (par exemple, des objets correspondant à des enregistrements dans une base SQL), le code est souvent généré automatiquement.

        Lorsque le compilateur rencontrerait un membre non présent, il appellerait l'opérateur "." (qui peut static_asserter si le nom n'est pas acceptable, ou remplir les champs au runtime.

        Ça c’est une très mauvaise idée. Si j’appelle un membre qui n’existe pas, je m’attends à une erreur de compilation, pas une erreur à l’exécution. Changer ça dans le contexte de C++, c’est, quelque part, casser le langage.

        Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

        • [^] # Re: Nouveau langage

          Posté par  . Évalué à 2.

          Ça c’est une très mauvaise idée. Si j’appelle un membre qui n’existe pas, je m’attends à une erreur de compilation, pas une erreur à l’exécution. Changer ça dans le contexte de C++, c’est, quelque part, casser le langage.

          C'est bien pour ça que ça devrait être quelque chose d'implémentable côté bibliothèque.

          Je pense qu'un des avantages du C++ c'est que faire des choix dans son code, ce n'est pas "casser le langage". Le langage nous offre des briques, et on est libre de construire ce que l'on veut avec. Avoir plus de briques n'est jamais une mauvaise chose, on est toujours libre de les laisser dans le bac à legos… mais par contre on peut toujours sortir la brique le jour ou on en a besoin (et j'ai déjà eu besoin plus d'une fois explicitement de cette fonctionnalité… c'est toujours possible de le refaire à la main ou avec des templates variadiques si on est motivé, mais bon).

        • [^] # Re: Nouveau langage

          Posté par  . Évalué à 1. Dernière modification le 06 décembre 2016 à 14:06.

          Certes, mais en même temps, exposer dans ta sérialisation le nom du membre (côté programme) est vraisemblablement une mauvaise idée.

          Je comprends ta remarque, mais ce dont tu parles c'est d'avoir la possibilité de filtrer le "nom" des membres. L'un n'empêche pas l'autre. Dans 95% des cas, tu veux un 1:1 entre la sérialisation et ta classe, donc le code devrait pouvoir faire cela directement, et pour les 5% restant, tu peux intercepter et/ou filtrer. La proposition d'introspection (qui sera dans C++20 probablement) le permettra justement.

          Ça c’est une très mauvaise idée. Si j’appelle un membre qui n’existe pas, je m’attends à une erreur de compilation, pas une erreur à l’exécution. Changer ça dans le contexte de C++, c’est, quelque part, casser le langage.

          Justement, c'est tout l'intérêt du constexpr ici. Par défaut, un tel opérateur entraînerait une erreur "membre machin non trouvé" donc le comportement sera 100% identique à l'état actuel.
          Par contre, si l'opérateur est présent, alors soit il est constexpr et peut être vérifié à la compilation (donc un comportement entre le mode dynamique/runtime et le mode figé/statique).
          Ceci serait très utile justement pour éviter les "outils" de génération qui écrivent du code illisible (type mock ou autre gsoap).
          Exemple d'application, les membres namespace:attribute du XML, l'opérateur pourrait vérifier que le nom du membre "inconnu" que l'on veut vérifier commence par le nom du namespace attendu, par exemple.
          Ou que les membres ajoutés commencent par "signal_" ou "slot_", si tu vois ce que je veux dire.

          Ensuite, si l'opérateur n'est pas constexpr, le compilateur pourrait te jeter pour chaque appel dont le nom du membre est fixe à la compilation (avec un warning du genre "Ce serait mieux si tu ajoutais un membre machin à ta classe").

          Enfin, tu l'utilises comme une classe dynamique au runtime (donc tu ne connais pas le nom des membres à priori), et tu utilises l'introspection pour les retrouver. C'est faisable actuellement avec une table de hash, mais c'est pas natif, donc pas optimal.

          En bref, tu as le choix, c'est justement ça pour moi l'intérêt principal du C++

          • [^] # Re: Nouveau langage

            Posté par  . Évalué à 2.

            J’ai un peu de mal à te suivre.

            Je vais essayer de reformuler ce que j’ai compris. Tu souhaites que :

            Foo foo;
            foo.baz = "toto";

            rajoute statiquement un membre baz de type string dans l’instance foo, et uniquement celle-ci ?

            Ça casse tout le modèle de compilation C++, et c’est contraire à tout ce à quoi je m’attends quand je lis du C++ (c’est pour ça que je parlais de « casser le langage »). En plus, tu peux très bien obtenir ce résultat en surchargeant l’opérateur [] :

            foo["baz"] = "toto";

            Ce qui certes fait 3 caractères de plus à taper, mais est aussi lisible et beaucoup plus « dans l’esprit » du langage. Quant à l’idée que ce ne serait pas optimal, dans tous les cas, c’est un lookup dans une table de hachage (ou un dictionnaire), je ne suis pas sûr que l’intégrer au compilateur gagnera grand chose.

            Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

        • [^] # Re: Nouveau langage

          Posté par  . Évalué à 3.

          Certes, mais en même temps, exposer dans ta sérialisation le nom du membre (côté programme) est vraisemblablement une mauvaise idée

          Dans la majorité des cas, non, ca change pas grand chose. C'est pas la meilleure idée de la création, mais c'est pas une hérésie non plus.
          Dans les cas qui restent, regarde ce que fait Jackson en Java, avec un système d'annotations qui permet de d'abstraire a 100% du nom des champs, et de leur type. C'est foutremenr efficace, et si j'avais un truc pareil en swift/objc, je virerais direct 10% de mon code.

          Linuxfr, le portail francais du logiciel libre et du neo nazisme.

      • [^] # Re: Nouveau langage

        Posté par  . Évalué à 1. Dernière modification le 06 décembre 2016 à 00:14.

        En attendant, on peut tricher un peu en faisant ça :

        #include <boost/unordered_map.hpp> 
        #include <variant>
        struct js_object;
        using js_value_t = std::variant<int, std::string, bool, js_object>;
        
        struct js_object
        {
            template<int N>
            constexpr void set(const char (&s)[N], const js_value_t& value) 
            {
              if ( str_equal(s, "foo") )
              {
                m_foo = std::get<int>(value);
              }
              else if ( str_equal(s, "the_bar") )
              {
                m_bar = std::get<int>(value);
              }
              else
              {
                m_other[s] = value;
              }
            }
            template<int N>
            constexpr js_value_t get(const char (&s)[N]) const
            {
              if ( str_equal(s, "foo") )
              {
                return m_foo;
              }
              else if ( str_equal(s, "the_bar") )
              {
                return m_bar;
              }
              else
              {
                return m_other.at(s);
              }
            }
        
          private:
            template<int N, int M>
            static constexpr bool str_equal(const char (&s)[N], const char (&s2)[M])
            {
              if constexpr (N == 0 || N != M)
                return false;
        
              for(int i = 0; i < N; i++)
              {
                if (s[i] != s2[i]) 
                  return false;
              }
        
              return true;
            }
        
            int m_foo = 0;
            int m_bar = 0;
            boost::unordered_map<std::string, js_value_t> m_other;
        };
        
        auto my_function(js_object& b)
        {
            b.set("the_bar", 1111);
            return std::get<int>(b.get("the_bar"));
        }
        
        auto other_function(js_object& b)
        {
            using namespace std::literals;
            b.set("random_value", "a string"s);
        }
        

        Avec GCC à -O3, my_function se simplifie.

        my_function(js_object&):
        mov DWORD PTR [rdi+4], 1111
        mov eax, 1111
        ret

        • [^] # Re: Nouveau langage

          Posté par  . Évalué à 1.

          Oui, mais c'est lourd à écrire, à lire et donc à maintenir. C'est ça le problème.

  • # Syntaxe ?

    Posté par  . Évalué à 3.

    Merci pour cette dépêche. Je suis un peu surpris par la syntaxe choisie. Je me serais attendu à quelque chose comme static_if<cond> {

    un peu sur le modèle du static_assert.

    Quelqu’un sait ce qui a motivé le choix de cette syntaxe ?

    Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

    • [^] # Re: Syntaxe ?

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

      Une question facile :)

      En C++, quand il s'agit d'ajouter quelque chose, ils cherchent avant toute chose à n'ajouter aucun nouveau mot clé. L'objectif est de n'introduire aucune régression sur des vieux codes qui compilaient. Ce qui fait que les évolutions se font en priorité dans la bibliothèque standard, puis par réutilisation/dépréciation de mots clés (cf auto).

      Avec static_if, il suffit d'une bibliothèque tierce (et je crois bien qu'il en existe justement) qui définit quelque chose avec ce nom, et cela plante définitivement la bibliothèque et tous les codes qui s'en servent.

      • [^] # Re: Syntaxe ?

        Posté par  . Évalué à 1.

        Normalement, chaque bibliothèque a son espace de nommage donc ça ne plante que si on utilise la bibliothèque comme un goret (cf. le std:: devant les fonctions de la bibliothèque standard).

        Cette signature est publiée sous licence WTFPL

        • [^] # Re: Syntaxe ?

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

          Sauf si le nouveau truc est un mot clé.
          Imagine une bibliothèque pré C++11 avec un my::constexpr. En C++11, ça ne peut plus compiler.

          • [^] # Re: Syntaxe ?

            Posté par  (site web personnel) . Évalué à 2. Dernière modification le 05 décembre 2016 à 15:59.

            Petite précision : Si le static_assert() devait être introduit aujourd'hui dans le C++ il aurait été renommé en constexpr_assert() car en C++ on essaye de faire la distinction entre la (les) sémantique(s) portée(s) par le mot-clé static et celle de constexpr.

            Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

  • # Retour arrière sur l'OCP

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

    Si cette évolution va simplifier l'écriture de fonctions récursives telles que les templates variadiques comme montré en exemple dans la dépêche, je redoute pour ma part un tour arrière sur le principe ouvert (aux évolutions) fermé (aux modifications).

    Souvenez-vous, c'est le principe qui dit que dans un monde OO, les if c'est mal, et que les fonctions virtuelles c'est mieux. L'idée est que quand nous identifions des points de variations, plutôt que de modifier le code pour ajouter des nouveaux cas à coups de if ou de switch, à la place, on prévoit un point de variation ouvert qui pourra accepter de façon plug-and-play des nouveaux cas non encore identifiés/définis.

    Si l'OO nous offrait un OCP dynamique, jusqu'à présent on employait des traits ou le SFINAE (c'est pas vraiment des traits le SFINAE) en métaprogrammation template. Pour ceux qui connaissent le Prolog (voire XSLT), on retrouve quelques part le principe de résolution par unification.

    C'est sûr qu'un if c'est plus simple et plus accessible. Mais c'est beaucoup moins évolutif.

    • [^] # Re: Retour arrière sur l'OCP

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

      s/tour/retour

      • [^] # Re: Retour arrière sur l'OCP

        Posté par  (site web personnel) . Évalué à 3. Dernière modification le 05 décembre 2016 à 15:00.

        C'est le principe ouvert/fermé.
        Connu en anglais sous le nom open/closed principle.
        (dans notre cas du C++, je trouve plus explicite le commentaire de lmg HS que l'article Wikipédia)

        Souvent, on préfère un code simple et fermé à à code complexe mais ouvert (évolutif).
        L'idée est d'avoir un code simple, facile à comprendre, et le jour où on a vraiment besoin de le faire évoluer, et bien on le réécrit.
        Le risque d'avoir un code complexe mais ouvert est de garder la complexité et de ne jamais profiter de son ouverture.

        Je précise, je suis pour le principe ouvert/fermé. Je dis juste que des fois (voir souvent) ce n'est pas justifié.

        Selon le contexte, on utilisera :

        • if constexpr pour du code simple et fermé ;
        • plusieurs templates pour du code complexe mais ouvert.

        Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

        • [^] # Re: Retour arrière sur l'OCP

          Posté par  . Évalué à 1.

          Je ne suis pas trop d’accord avec toi (du moins, avec ce que tu as écris). Tu donnes l’impression que ouvert et fermé s’opposent, alors que dans l’OCP, ils se complètent (n’agissent pas sur la même dimension). L’article wikipédia en français est assez clair là-dessus.

          La complexité du code n’a pas nécessairement grand chose à voir là-dedans (au contraire, bien mis en œuvre, l’OCP a tendance à simplifier l’architecture globale car il réduit les effets de bord).

          Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

          • [^] # Re: Retour arrière sur l'OCP

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

            Oui, tu as raison whity, merci. Je corrige :-)

            Je résume le commentaire de lmg HS qui en bref disait que nous avons le choix entre :

            • if constexpr pour du code simple ;
            • plusieurs templates pour du code complexe mais ouvert/fermé.

            Voici mon commentaire corrigé

            if constexpr simplifie le code et tan pis pour le principe ouvert/fermé.
            Car souvent, on préfère un code simple plutôt qu'un code complexe mais ouvert/fermé (évolutif).

            L'idée est d'avoir un code simple, facile à comprendre, et le jour où on a vraiment besoin de le faire évoluer, et bien on le réécrit.
            Le risque d'avoir un code complexe mais ouvert/fermé est de garder la complexité et de ne jamais profiter de son principe ouvert/fermé.

            Je précise, je suis pour le principe ouvert/fermé, et c'est mieux quand on peu allier simplicité à ouvert/fermé.
            Je dis juste que des fois (voir souvent) complexifier n'est pas justifié, vaut mieux avoir simple et risquer tout casser.

            Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

  • # Correction § "Remplacement du SFINAE"

    Posté par  (site web personnel) . Évalué à 3. Dernière modification le 05 décembre 2016 à 15:09.

    Le code suivant est utilisé dans la dépêche, mais il est ambigu car on ne voit pas la différence au niveau de la déclaration des deux fonctions :

    template<class T>
    auto f(T x) -> decltype(std::enable_if_t<std::is_function_v<decltype(T::f)>,int>{})
    {
      return x.f();
    }
    
    template<class T>
    auto f(T x) -> decltype(std::enable_if_t<!std::is_function_v<decltype(T::f)>,int>{})
    {
      return 0;
    }

    Merci à un modérateur de le remplacer par :

    template<class T>
    auto f(T x) -> decltype(std::enable_if_t<   std::is_function_v<decltype(T::f)>,int>{})
    {                                      // ^---true si T::f existe et que c'est une fonction
      return x.f();
    }
    
    template<class T>
    auto f(T x) -> decltype(std::enable_if_t< ! std::is_function_v<decltype(T::f)>,int>{})
    {                                      // ^---le contraire
      return 0;
    }

    Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

  • # Je m'y colle...

    Posté par  . Évalué à 10.

    Chère lectrice, cher lecteur LinuxFr.org. Souhaites-tu une dépêche consacrée au SFINAE ? Alors exprime-toi dans les commentaires, et de nombreuses personnes vont certainement unir leur forces pour t’offrir un superbe article sous licence libre. Bon, si tu n’oses pas demander, personne n’aura l’impulsion pour se lancer…

    Puisqu'il faut demander, je m'y colle : on pourra avoir une dépêche sur le SFINAE ? Parce que j'ai à peu près rien compris ! Comme ça, je n'aurai sans doute toujours rien compris, mais j'aurai plus à lire ! :-)

    • [^] # Re: Je m'y colle...

      Posté par  (site web personnel) . Évalué à 3. Dernière modification le 05 décembre 2016 à 15:45.

      OK on rajoute ça dans le tuyau ;-)
      On commencera par quelques petites dépêches sur la méta-programmation, puis on attaquera gentiment le SFINAE ^
      Mais soit patient, ça risque d'être pour l'année prochaine !

      Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

      • [^] # Re: Je m'y colle...

        Posté par  . Évalué à 3.

        En attendant, il y a une explication relativement claire en français ici.

        Mes commentaires sont en wtfpl. Une licence sur les commentaires, sérieux ? o_0

        • [^] # Re: Je m'y colle...

          Posté par  . Évalué à 1.

          Hum, en fait je crois que comme M. Jourdain qui faisait de la prose sans le savoir je faisais de la SFINAE. C'est une des bases de la métaprogrammation en fait, tout simplement. Si on en a fait un tout petit peu, on a forcément exploité cette technique.

          Ce commentaire est libre de droit, vous pouvez le réutiliser comme bon vous semble.

          • [^] # Re: Je m'y colle...

            Posté par  . Évalué à 3.

            Oui c'est pour ça que je ne vois pas trop pourquoi en faire un article (avec 2 articles d'introduction en plus !).
            Mais je rate probablement quelque chose…

            Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

            • [^] # Re: Je m'y colle...

              Posté par  . Évalué à 4.

              N'ayant pas compris un traitre mot du lien ci-dessus, je suis favorable à un article, ou un autre lien qui supposerait moins de connaissance de la part du lecteur.

              Parce que j'ai pas mal entendu parlé de template et de métaprogrammation, mais je n'ai jamais compris ce que c'était (ni à quoi ça sert).

              • [^] # Re: Je m'y colle...

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

                Pas facile en effet de trouver de bonnes explication en français de SFINAE. Google me donne ceci: http://h-deb.clg.qc.ca/Sujets/Divers--cplusplus/SFINAE.html
                Est-ce que ça aide ?

                j'ai pas mal entendu parlé de template et de métaprogrammation, mais je n'ai jamais compris ce que c'était (ni à quoi ça sert).

                Le problème n'est peut être pas SFINAE, mais la métaprogrammation tout court.
                Quelques liens:
                http://loulou.developpez.com/tutoriels/cpp/metaprog/
                http://h-deb.clg.qc.ca/Sujets/Divers--cplusplus/Metaprogrammation.html

              • [^] # Re: Je m'y colle...

                Posté par  . Évalué à 6. Dernière modification le 06 décembre 2016 à 10:05.

                C'est de la programmation fonctionnelle à la compilation. Un bon exemple est la fonction factorielle.

                Tu définis une structure factorial qui dépend d'un paramètre template qui est un entier :

                template <std::size_t N>
                struct factorial
                {
                };

                À l'intérieur tu définis le cas général :

                static constexpr std::size_t value = RangeN * factorial<RangeN - 1>::value;

                constexpr veut dire que c'est calculé à la compilation. C'est faisable puisqu'on a toutes les infos à la compilation. Le code est simple, value est égal au rang actuel multiplié par factorielle du rang moins un.

                Comme dans toute programmation récursive (la programmation fonctionnelle se fait en récursion), il faut une condition d'arrêt :

                template<>
                struct factorial<0u>
                {
                  constexpr static std::size_t value = 1u;
                };

                La valeur de factorielle de 0 est un. C'est ici que la récursion s'arrête.

                Un programme complet pour montrer le code :

                #include <iostream>
                
                template <std::size_t RangeN>
                struct factorial
                {
                  static constexpr std::size_t value = RangeN * factorial<RangeN - 1>::value;
                };
                
                template<>
                struct factorial<0u>
                {
                  constexpr static std::size_t value = 1u;
                };
                
                int main()
                {
                  std::cout << factorial<0>::value << "\n";
                  std::cout << factorial<4>::value << "\n";
                }

                factorial<0>::value et factorial<4>::value sont connus à la compilation, rien n'est calculé au runtime.

                Cet exemple là n'est pas très utile, mais il existe des cas où ces technique améliorent beaucoup la rapidité du code en précalculant des choses à la compilation. Glander sur http://stackoverflow.com/questions/tagged/metaprogramming%20c%2b%2b peut donner quelques exemples d'application.

                Ce commentaire est libre de droit, vous pouvez le réutiliser comme bon vous semble.

                • [^] # Re: Je m'y colle...

                  Posté par  . Évalué à 1.

                  Merci à vous deux. Si j'ai bien compris, SFINAE, c'est simplement l'idée d'aller chercher la fonction la plus spécialisée ; ça me fait penser au select type du fortran 2003.

                  Pour le constexpr, de ce que je lisais sur les sites donnés, il n'y a même pas besoin de template. C'est juste un moyen simple de pré-calculer des valeurs, tout en les calculant à l’exécution lorsque c'est impossible à la compilation.

                  • [^] # Re: Je m'y colle...

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

                    Le SFINAE est un outil qui sert à détecter des informations non triviales ou des possibilités par essai et erreur. Sauf que les erreurs sont ignorées et à la place on se débrouille pour récupérer un truc que l'on va assimiler à faux. Ce booléen on s'en sert ensuite ailleurs pour choisir quelle spécialisation nous intéresse.

                    Exemple à la C++98

                    template <typename T>
                    struct has_typedef_montype
                    {
                        using yes = char[1];
                        using no  = char[2];
                    
                        // Signatures qui renvoient des choses différentes
                        // - Version qui prend ce que l'on cherche
                        template <typename C> static yes& test(typename C::montype*);
                        // - Version qui prend n'importe qui
                        template <typename C> static no & test(...);
                        // NB, on n'a pas besoin de définition, mais de renvoyer une réf.
                    
                        // Ici le compilo tente de résoudre. 
                        // Si T permet d'extraire un type `montype*`, alors on renvoie `yes` qui a une taille de 1
                        // Sinon, on défaulte sur la version variadique qui renvoie un `no` de taille 2
                        // Ce qui permet de construire le booléen `value` grâce à sizeof qui est toujours résolu à la compilation
                        static const bool value = sizeof(test<T>(NULL))==sizeof(yes);
                    };
                    
                    struct U { using montype = int; };
                    
                    int main ()
                    {
                        std::cout << std::boolalpha; // affiche true ou false
                        std::cout << has_typedef_montype<int>::value << "\n";
                        std::cout << has_typedef_montype<U>::value << "\n";
                    }

                    Façon C++14 (voire 17), on a de nouveaux outils:

                    #if 0
                    // C++17
                    // Requiert gcc 5.0
                    template <typename...T> using void_t = void;
                    #else
                    // Pour gcc v < 5.0
                    // Until CWG 1558 (a C++14 defect), unused parameters in alias templates
                    // were not guaranteed to ensure SFINAE and could be ignored, so earlier
                    // compilers require a more complex definition of void_t, such as
                    template<typename... Ts> struct make_void { typedef void type;};
                    template<typename... Ts> using void_t = typename make_void<Ts...>::type;
                    #endif
                    
                    // Cas par défaut
                    template <typename T, typename = void>
                    struct has_typedef_montype
                    : std::false_type
                    {};
                    
                    // Cas où l'on arrive à trouve le typedef qui nous intéresse
                    template <typename T>
                    struct has_typedef_montype<T, void_t<typename T::montype>>
                    : std::true_type
                    {};
  • # SFINAE

    Posté par  . Évalué à 1.

    Deux vidéos intéressantes sur la metaprogrammation et les SFINAE :

    CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium (partie 1 et 2)

    https://www.youtube.com/watch?v=Am2is2QCvxY
    https://www.youtube.com/watch?v=a0FliKwcwXE

  • # dans l'exemple, is_function ne marche pas, il faut utiliser is_member_funciton_pointer

    Posté par  . Évalué à 1.

    Dans l'exemple donné avec if_function, en fait ça ne marche pas avec gcc 7 ni le dernier clang, parce que T::f n'est pas une fonction statique.

    Voir ici l'erreur de compilation:
    https://godbolt.org/g/qIPNxg

    En revanche avec is_member_function_pointer on y arrive:
    https://godbolt.org/g/NNbYBw

    Il y a peut-être d'autre façon de corriger, je ne suis pas un gourou méta-programmation.

    L'exemple cité dans le corps de la dépêche ne compile que parce que le template n'est pas instancié mais seulement défini.

    Voilà, vivement c++17, et merci pour la dépêche instructive.

Suivre le flux des commentaires

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