C++17, Genèse d’une version mineure

Posté par (page perso) . Édité par Benoît Sibaud, Davy Defaud, cracky, palm123, Lucas, Adrien Dorsaz, M5oul, Martin Peres, ZeroHeure, Storm et RyDroid. Modéré par Yvan Munoz. Licence CC by-sa
Tags :
61
2
oct.
2016
C et C++

La série de dépêches C++ continue. Cette seconde dépêche nous amène dans les réunions du comité de standardisation en vue de publier la prochaine version C++17 et nous permettra de vérifier ce titre provocateur (comment ça mineure ?). Cette dépêche peut intéresser tous les lecteurs de LinuxFr.org, pas seulement les développeurs. Les prochaines dépêches seront plus techniques.

Deux collègues discutent : « C++ est enfin sorti », « Trop top », « Va falloir se palucher les 1700 pages du nouveau standard », « Gloups ». Une note repositionnable sur le dessin indique : « Il y en a qui ne connaissent pas encore LinuxFr.org »

Sommaire

Dépêches C++

Cette dépêche est la deuxième d’une série de cinq dépêches sur le C++. La première dépêche Les coulisses du standard C++ a été publiée fin août dernier.

  1. La dépêche précédente, Les coulisses du C++, présente la naissance du langage, sa longue normalisation, sa spécification officielle non libre, payante, ouverte, délaissée au profit de son brouillon (draſt), peu lue par les développeurs C++…

  2. Cette dépêche, Genèse du C++17, raconte les dernières réunions du comité de normalisation et donne des éléments pour expliquer ce long processus de normalisation.

  3. La troisième dépêche, Nouveautés C++17 du langage, présentera les changements au niveau du langage : déduction des arguments template std::array a{1,2,3} ;, décomposition du retour de fonction auto [x,y]=f() ; , namespace aa::bb{} équivalent à namespace aa{namespace bb{}}, if constexpr sélectionne du code à la compilation, lambda constexpr, lambda capture *this, if(init;condition) comme for(init;cond;inc), variables inline… Mais il faudra encore attendre pour l’intégration des Concepts, Modules, Syntaxe d’appel uniforme et Réflexion.

  4. La quatrième dépêche, Nouveautés C++17 de la bibliothèque standard, présentera les changements au niveau de la bibliothèque standard qui pourraient bien bousculer notre petite vie de développeur : algorithmes parallélisés, std::string_view, std::filesystem, std::variant, std::any, std::optional, les fonctions spéciales mathématiques… Mais, les intervalles (ranges) et le réseau (networking) seront intégrés pour une version suivante du C++.

  5. Bilan C++17 et attentes pour C++20. Version mineure ou majeure ? D’un côté, les améliorations sont nombreuses et appréciables. Mais de l’autre, aucune fonctionnalité majeure n’est intégrée, exceptées celles qui sont déjà disponibles dans Boost. Cette cinquième dépêche s’attaquera aux questions existentielles du C++ : Quelles conséquences sur le processus de normalisation ? Qu’attendre de C++20 ? Intérêt du C++ aujourd’hui ? Quels langages alternatifs ? Que faire pour dynamiser l'évolution du C++ ? S’impliquer ?

  6. … d’autres dépêches à venir. :-)

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

Partage

Chère lectrice, cher lecteur LinuxFr.org. Tu souhaites donner un coup de main pour les dépêches suivantes ? Rejoins‐nous dans l’espace de rédaction collaborative sur LinuxFr.org. Un compte est nécessaire pour y accéder.

Après publication, les dépêches sont figées sur LinuxFr.org. Alors, pour continuer à améliorer ce contenu libre (fôtes, oublis, franglais, maladresses…), n’hésite à pas à aller sur le dépôt Git C++FRUG. Tu y trouveras les versions de ces dépêches les plus à jour :

  1. Les coulisses du standard C++ ;
  2. Genèse d’une version mineure ;
  3. Nouveautés du langage ;
  4. Nouveautés de la bibliothèque standard ;
  5. Bilan et attentes pour C++20.

Avec toutes nos contributions réunies, nous profiterons davantage de nos découvertes individuelles et nous offrirons un contenu CC BY-SA de qualité pour créer, par exemple, des supports de formation (Meetups), des articles sur d’autres blogs… Par contre, pour le moment nous ne pouvons pas encore en faire profiter Wikipédia : notre dépêche étant en CC BY-SA version 4.0 et Wikipédia encore sur la version précédente :-/

Deux sommets pour délimiter le périmètre C++17

Comme le prévoit le cycle de publication triannuel, c’est en 2016 (année N-1) que le comité de normalisation du C++ vote le périmètre fonctionnel du C++17. Ainsi, les membres du comité se sont réunis à deux reprises pour intégrer de nouvelles fonctionnalités au C++ :

  1. Une semaine début mars, à Jacksonville (Floride), pour adopter beaucoup de fonctionnalités, mais aussi pour rejeter plusieurs fonctionnalités majeures très attendues ;
  2. Une semaine fin juin, à Oulu (Finlande), pour intégrer pas mal d’autres fonctionnalités et ainsi clore l’ajout des fonctionnalités.

La semaine se déroule sur six jours, du lundi au samedi. Pas de grasse matinée car les premières réunions débutent à 8h30. Ces réunions permettent de débattre et voter l’intégration de chacune des spécifications techniques (Technical Specification). Plusieurs réunions se déroulent en parallèle, et les membres rejoignent les réunions selon leur centre d’intérêt. Après ces réunions officielles, les membres se retrouvent pour continuer à échanger ou pour améliorer les spécifications techniques.

Mais à Oulu, un phénomène naturel a eu un impact direct sur la productivité : le soleil se couche après minuit en juin ! Si bien, que la plupart des membres ne se rendaient pas compte de l’heure et ont continué à bosser bien plus tard que d’habitude. En plus du soleil qui dort seulement deux heures par nuit, le décalage horaire (jetlag) a achevé les non-européens qui ont eu besoin de plusieurs jours de repos pour s’en remettre !

Voici à quoi ressemble le soleil à une heure du matin au mois de juin (ici c'est à Tromsø à 600 km de Oulu) Le soleil n'est pas encore couché à une heure du matin dans la ville de Tromsø à 600 km de Oulu

Des racines C++17 très profondes

Les membres du comité de standardisation C++ n’avaient pas pu intégrer toutes les fonctionnalités qu’ils souhaitaient dans C++11 car cela aurait retardé d’autant plus la publication de cette version (déjà que la publication était prévue avant 2010…). Les membres avaient donc décidé d’intégrer les fonctionnalités mineures dans C++14 et de continuer à mûrir les fonctionnalités majeures pour C++17.

Par conséquent, C++17 n’a pas commencé à être construit au lendemain de la publication de C++14, mais bien avant : certaines parties datent du début des années 2000 !

Analogie entre les fonctionnalités promises pour C++17 et les promesses des candidats à la présidentielle de 2017 en France

Plus de 10 ans pour intégrer les fonctionnalités

Effectivement, certaines fonctionnalités sont dans le tuyau depuis plus de dix ans :

Par contre, d’autres fonctionnalités majeures sont toujours dans le tuyau :

Comme quoi, le comité de standardisation prend son temps pour bien s’assurer que chaque fonctionnalité soit parfaite et cela peut prendre une dizaine d’années ! L’objectif étant de ne pas dégrader d’avantage la complexité inhérente au C++, avec comme contre partie d’avoir un langage de programmation qui évolue doucement…

Deux chatons déçus du contenu de C++17 "Sniff.. On n'a pas les Concepts. Ni la Réflexion."

Mais pourquoi autant de temps ?

Avant de répondre à cette question, voici un petit exercice. Il faut trouver la correction pour que le code C++ suivant compile :

struct MaClasse
{
    template <class T>
    void f() { }
};

template <class T>
void maFonction (T& t)
{
    t.f<int>(); //expected primary-expression before 'int'
}

int main()
{
    MaClasse maclasse;
    maFonction(maclasse);
}

Quelques compilateurs en ligne pour tester tes idées :

Le message d'erreur du compilateur GCC :
(le texte du message n'a pas changé entre GCC-4.4 et GCC-6.2)

$ g++ enigme.cpp
enigme.cpp: In function ‘void maFonction(T&)’:
enigme.cpp:10:12: error: expected primary-expression before ‘int’
     t.f<int>(); //error: expected primary-expression before 'int'
         ^~~
enigme.cpp:10:12: error: expected ‘;’ before ‘int’

Allez une grosse image pour ne pas être tenté de lire la réponse tout de suite.

logo "The C++ Programming Language"

Allez, deux indices : Primo, c’est du vieux C++98 (ne cherchez pas midi à C++14 heures) ; Secundo, ci-dessous le message d’erreur fourni par le compilateur Clang-3.8.

$ clang++ enigme.cpp                                                    
enigme.cpp:10:7: error: use 'template' keyword to treat 'f' as a dependent template name
    t.f<int>(); //error: expected primary-expression before 'int'
      ^
      template

Les plus rapides qui publient en commentaire une solution sans regarder la réponse ont gagné :-) Les vainqueurs auront le droit de poser en commentaire d'autres énigmes sur le thème des bizarreries du C++.

Encore une grosse image. On joue le jeu, on ne triche pas !

le texte "C++" sur un fond de type "écran du film Matrix" (Copyright webblaster48 2009 CC-BY-NC-ND-3.0)

Réponse

Il manquait juste le mot clef template à un endroit un peu inattendu :

struct MaClasse
{
    template <class T>
    void f() { }
};

template <class T>
void maFonction (T& t)
{
    t.   template   f<int>();   // Oh le piège !
}

int main()
{
    MaClasse maclasse;
    maFonction(maclasse);
}

Cette syntaxe permet de dire au compilateur que T::f() est template quelque soit le type T (plus précisément, ici en absence du mot clef typename, le compilateur sait que c’est un appel à une fonction template). En effet, sans le mot clef template dans maFonction(), le compilateur ne peut pas être sûr que T::f() soit une fonction template. Le compilateur pourrait seulement s’en douter et le vérifier lors de l’instanciation de maFonction<MaClasse>()

Avouons que ce n’est pas très joli joli, non ?

Ceux qui ont décidé de cette syntaxe doivent s’en mordre les doigts.

Mais revenons en à notre question, Mais pourquoi autant de temps ?
L’hypothèse est que ce type de décisions du passé a traumatisé les membres du comité de normalisation du C++ : « ne recommençons pas les mêmes erreurs, ne nous précipitons pas, prenons le temps de bien mûrir les nouvelles fonctionnalités… »

Chère lectrice, cher lecteur de LinuxFr.org, à ton avis, pourquoi aussi peu de fonctionnalités majeures dans C++17 ?

En 2003, Bjarne Stroustrup propose la première version des Concepts. Ce "comic strip" imagine alors Bjarne demander l'intégration des Concepts pour chacune des versions de C++ : C++03, C++11, C++14, C++17... et à chaque fois les Concepts ne sont pas intégrés. Sur la dernière vignette, les Concepts sont enfin intégrés (de très nombreuses décennies plus tard). Sur cette dernière vignette est dessinée une pierre tombale avec une voix qui sort "Ce n'est pas trop tôt !".

C++1z ou C++17 ?

Historiquement, les versions C++ n'étaient pas publiées à date fixe. De plus, la version C++ qui devait suivre C++03 avait été reportée à maintes reprises. Cette version a été nommée temporairement C++0x. Et, finalement, C++0x a été publié en 2011 ! (mais bon en hexadécimal 0x = 11 est correct avec x = B)

Afin d’éviter tout nouveau glissement, le comité a alors décidé de publier un nouveau standard C++ tous les trois ans, en figeant les fonctionnalités l’année n - 1. Avec un cycle d’une version majeure (C++11) suivie d’une version mineure (C++14).

Malgré des dates de publication figées, les appellations C++1y (pour C++14) et C++1z (pour C++17) perdurent. Par exemple, l’option de compilation -std=c++1z ou l’étiquette c++1z sur stackoverflow.

Donc, C++1z est utilisé par les sceptiques pour désigner la version qui succédera à C++14. Et C++17 est utilisé pour désigner la version qui sortira en 2017.

Les membres du comité de normalisation utilisent le terme C++17 (et non pas C++1z). Soyons confiants, C++1z verra bien le jour en 2017 (et non pas en 2018, ni après).

La suite…

La prochaine dépêche va nous permettre d’entrer enfin dans le vif du sujet C++17.

Merci de nous donner un coup de main à la rédaction des prochaines dépêches C++17. Pour participer ou lire en avance les prochaines dépêches :

Droit d’auteur, licences, remerciements

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.

Merci aux nombreux auteurs sur le dépôt Git et sur LinuxFr.org : olibre, duckie, rom1v, Oliver H, Benoît Sibaud, cracky, palm123, Lucas, Adrien Dorsaz, RyDroid, M5oul, Storm et Martin Peres. Cette dépêche fut commencée en juin 2016 et contient plus de 1300 révisions !

Merci à Klaim, Édouard A, rewind, et gasche pour leurs commentaires pertinents.

Merci à mes collègues développeurs qui, à défaut de m’aider à la rédaction, ont illustré cette série de dépêches avec des dessins humoristiques sous licence libre :

Et merci à ceux qui permettent de réutiliser leur travail :

Merci d’avance de l’aide apportée sur les prochaines dépêches C++17 en cours de préparation : Micka pour ses exemples utiles et AMB007 pour les bogues trouvés dans les codes C++ d’exemple.

  • # Donc pour résumer…

    Posté par . Évalué à 10.

    Il y a des concepts super intéressants mais bien complexes qui vont s’ajouter à un langage déjà hyper complexe et assez peu intéressant dès qu’on sort du dogme « perfs à tout prix », mais faudra attendre sans doute quelques dizaines d’années. En attendant, des langages moins prise de tête sortent régulièrement et comblent les très importantes lacunes du C++, à savoir que globalement il tire encore de nos jours des fonctionnalités de lib affreusement étendues et complexes à maintenir telle que la lib boost (à tel point qu’ils se sont fait des outils spécifiques pour gérer la maintenance) voire purement et simplement une reprise des lib du C.

    Il y a des simplifications qui arrivent, très bien, elles auraient dû être là il y a bien longtemps. Je ne vois pas l’intérêt aujourd’hui de débuter un programme en C++ s’il n’y a pas un impératif de performances (et avec des bibliothèques déjà développées dans ce but).

    Néanmoins, félicitations pour la dépêche. Je pense que cela demande une certaine dose de courage pour continuer à s’investir dans ce langage.

    • [^] # Re: Donc pour résumer…

      Posté par . Évalué à 3.

      Pourrais-tu expliciter un peu ce que tu lui reproche à ce langage ?

      • [^] # Re: Donc pour résumer…

        Posté par . Évalué à 10.

        Je ne suis pas l'OP, mais :

        • erreurs catastrophiques ;
        • temps de compilation ;
        • le préprocesseur, vraiment ?
        • le langage de templates qui est parmi les plus abscons, parce qu'il mélange les génériques et la métaprogrammation (ou alors je suis le seul à trouver les templates récursifs imbitables) ;
        • parfois de la complexité superfétatoire (= delete, un destructeurs privé virtuel par exemple) ;
        • les restrictions contre-intuitives (pourquoi on ne peut pas définir une classe template ailleurs que dans son header ?) souvent dues au préprocesseur ;
        • l'évolution rachitique (de l'aveu même de la dépêche, les gars n'ont pas réussi à définir une lib standard réseau ou fichiers en 10 ans) ;
        • la gestion mémoire qui accumule plusieurs couches au fil des années (pointeurs nus, références, puis pointeurs intelligents [unique_ptr/shared_ptr/etc.]) ;
        • lib standard parfois surprenante (on a une distribution gaussienne, mais pas de vector::delete_at)
        • rétro-compatibilité avec le C, mais pas vraiment non plus.

        Personnellement, j'aime bien le C++ et je le préfère à bien d'autres langages, mais il faut reconnaître ses défauts ; dont une bonne partie sont ÀMHA imputable à son âge.

        • [^] # Re: Donc pour résumer…

          Posté par . Évalué à 0.

          • erreurs catastrophiques : c'est à dire ? Si tu fais référence aux accès mémoires invalides, il est facile aujourd'hui d'écrire du code qui ne génère pas plus de segfault que de NULLPointerException.
          • temps de compilation : je ne rentre pas çà dans les lacunes très importantes ; de plus encore une fois pour avoir vu de l'environnement Java complexe, c'est pas forcément plus rapide ; aujourd'hui avec l'intégration de CLang et autres, c'est déjà plus confortable
          • le préprocesseur, vraiment ? trop vague ; sorry ; j'ai pas souvenir de préprocesseur qui soit meilleur ailleurs, quand il y a ; qu'est-ce qui te manque et qui existe ailleurs ?
          • le langage de templates qui est parmi les plus abscons, parce qu'il mélange les génériques et la métaprogrammation (ou alors je suis le seul à trouver les templates récursifs imbitables) ; je partage ton sentiment mais est-ce réellement un problème ? Le templating pose problème aux gens utilisant vraiment le template (les boss quoi), car pour les cas les plus simple j'ai le sentiment que c'est pas plus compliqué que Java pour la très grande des développeurs
          • les restrictions contre-intuitives (pourquoi on ne peut pas définir une classe template ailleurs que dans son header ?) souvent dues au préprocesseur ; peux-tu donner un exemple autre que le template ?
          • l'évolution rachitique (de l'aveu même de la dépêche, les gars n'ont pas réussi à définir une lib standard réseau ou fichiers en 10 ans) : bof ; la culture est tout simplement pas la même ; en C++ on a l'habitude chercher des lib/framework (Asio/Boost, Qt, etc) ; la question est plutôt qu'est-ce qui devrait être là aujourd'hui ?
          • la gestion mémoire qui accumule plusieurs couches au fil des années (pointeurs nus, références, puis pointeurs intelligents [unique_ptr/shared_ptr/etc.]) : ils répondent tous à des besoins différents, et je les utilise tous ; après si quelqu'un trouve cela compliqué, il n'a qu'à utiliser les smart pointer ; non ?
          • lib standard parfois surprenante (on a une distribution gaussienne, mais pas de vector::delete_at) : pas d'accord car c'est surtout une manière de coder pour l'exemple que tu donne, ensuite cela rejoint le point concernant l'évolution
          • rétro-compatibilité avec le C, mais pas vraiment non plus : pour quelqu'un codant en C++ c'est pas gênant ; je vois pas où est le problème

          Oui le C++ permet de travailler à différents niveaux (plus ou moins proche de la machine), oui il faut choisir les outils que l'on va utiliser (aussi bien en terme de librairie que de fonctionnalité du langage). Mais si on désire un tout en un intégré on choisit juste autre chose, non ? Java est pas mal, Python aussi, et il y en a tant d'autres.

          Après que l'on puisse faire mieux, je partage le sentiment, et il me semble qu'ils y travaillent (ils enlèvent même des trucs !!).

          Je ne vois rien là dedans de catastrophique et j'ai pas sentiment que le C++ soit lacunaire (j'voudrais bien la Réflexion !). Je pense qu'à l'instar de Linux en tant qu'OS, le C++ n'a pas vocation à remplacer/dominer tous les autres langages.

          • [^] # Re: Donc pour résumer…

            Posté par . Évalué à 10.

            • le preprocesseur, le problème c'est son existence même. Le remplacement de chaînes de textes à la rache, c'était raisonnable en 77, plus vraiment en 2000.
            • les temps de compilation sont long, tres long. Et non, Java n'est clairement pas dans le même ordre de grandeur. Java peut être long sur les builds de release, quand tu lances les validateurs de styles, la génération de doc, les tests unitaires et tout ce genre de trucs. Mais tu fais tout ça pour une bonne raison, et la majeure partie à pas grand chose à voir avec le langage, mais la release. Sur un build de dev dans un ide, Java est plutôt rapide. Déjà rien que zapper le linker, quand on voit le temps que ca peut prendre sur un projet c++ costaud… Et c'est une métrique importante, tout ce qui contribue à une loop de feedback rapide est important
            • les generics Java ont leurs problèmes clairement (cough type erasure cough), mais bon. Ce que tu dit la, c'est en gros "les templates c++ sont un bordel sans nom, mais si tu te limites à juste une partie, c'est comme les autres langages". Sauf que tu te tapes toujours une compilation du template à chaque instantiation sur un type donne. Et ca rejoins l'autre critique: toutes ces features ont un coût, il faut les comprendre, savoir quand ne pas les utiliser, et faire doublement gaffe en code review.
            • la culture est pas la même sur la lib std: oui, c'est précisément le problème, cette culture.

            'fin c'est chelou, tu demandes quels sont les points qui posent problèmes, quand on te les donne tu réponds en substance "ouais, c'est des problèmes, mais c'est pas grave, si on ignore les problèmes, y'a plus de problèmes".
            Ben ouais, forcément.

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

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 3.

              les temps de compilation sont long, tres long.

              Je ne le nie pas, les temps de compilation C++ peuvent être longs. Ceci étant dit, quelle est la part de lenteur due au langage, et quelle est la part due aux outils, make, par exemple?
              Parce que selon certains, pour une compilation incrémentale, make est inutilement très lent.

              Et aussi, je pense que comparer uniquement les temps de compilation est une erreur de nos jours. Après tout, si on prend Java ou C#, il me semble bien qu'une partie de la compilation est faite au lancement de l'exécutable.
              Du coup, ce qui serait objectif, c'est de comparer compilation + temps de première exécution, non?

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 1.

                Du coup, ce qui serait objectif, c'est de comparer compilation + temps de première exécution, non?

                Si on compare à un langage à VM, effectivement. Mais dans ce cas, je faisais une comparaison implicite de mon expérience avec Rust (parce que c'est celui que j'utilise le plus en ce moment), qui est aussi compilé comme C++.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 1. Dernière modification le 03/10/16 à 00:53.

                Je ne pense pas que ce soit pertinent : les langages qui compilent au lancement ne sont pas les plus adaptés à remplacer le C++. Deux catégories, deux outils peu comparables.

                Et si on avait un jour un langage orienté système (pas de GC, compilé…) avec une bonne gestion des dépendances et avec une programmation haut niveau façon langages de script (voire Java mais en moins verbeux), alors il est envisageable (selon moi) que nous ayons un remplaçant aux langages dynamiques. Seuls resteront ceux qui, à l’instar de Perl 5, ont été pensés pour un usage spécifique (par exemple la manipulation de texte pour Perl). Bien entendu, je n’exclus pas les hybridations, les attachements personnels ou financiers (investissement sur le long terme) ainsi que l’historique lié aux autres langages qui feront qu’on continuera à les utiliser pendant des décennies. Mais pour des nouveaux projets…

                Peut-être qu’il y a un usage extrêmement important et utile des langages dynamiques que je ne vois pas. Mais selon mon point de vue, ils arrivent presque tous à leur terme. Je ne connais pas Python, mais Perl 6 est bien moins pratique pour manipuler rapidement du texte que Perl 5 (surtout avec nos bonnes vieilles habitudes), Ruby glisse doucement vers le compilé avec Crystal… si on arrive facilement à faire de la cross-compilation, je ne vois pas l’intérêt d’un interpréteur.

                Bref, je m’égare, je rêve… il est temps de dormir. :)

                • [^] # Re: Donc pour résumer…

                  Posté par (page perso) . Évalué à 1.

                  Perl 6 est bien moins pratique pour manipuler rapidement du texte que Perl 5 (surtout avec nos bonnes vieilles habitudes)

                  Curieux, sur ce point j'ai pas remarqué de grandes différences : les regexp, substitutions, split, etc. c'est relativement similaire en dehors de détails cosmétiques, et le langage vient même de base avec quelques fonctions comme slurp et spurt, sans devoir charger de module supplémentaire. Après si dans le « rapidement » on inclut le temps d'exécution, alors oui, là je suis bien d'accord, ça rame encore beaucoup trop sur des trucs aussi simples qu'un split sur une chaîne de caractères avec une regexp simple (le langage a aussi le défaut d'être probablement trop gigantesque, mais ça, ça fait partie de la philosophie du langage, donc, bon).

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 6.

                  Ah, tu oublies Rust. Pas de GC, bas niveau, elegant,une gestion de la la memoire au petits oignons…

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 3.

                    … et une portabilité et un nombre de libs disponibles très en dessous de C++

                    mais ça changera peut-être

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 2.

                    Je ne l’oublie pas. En fait, c’est sans doute le langage qui m’attire le plus (au moins sur le papier) pour faire de la programmation système bas niveau. Je me tâte à l’apprendre et à le proposer en projet à mes étudiants.

                    Depuis quelque temps, je me demande quels sont les langages dont j’aurai vraiment besoin, et qu’il faudrait vraiment que j’apprenne, pour avoir des outils adaptés à (presque) toutes les situations. Je suis fan de Haskell, pour plein de raisons, mais pour de la programmation système c’est pas aussi intuitif que le C. Du coup, je me garderai bien du Haskell pour du haut niveau (des applications en général, un peu complexes, avec un petit paquet de fonctionnalités) et partir vers du Rust pour le système. Les deux ont une bonne communauté, des outils modernes pouvant gérer les dépendances, des compilateurs qui peuvent détecter facilement des erreurs à la compilation plutôt qu’à l’exécution (voir la gestion de la mémoire en Rust, et la prise en compte des « corner cases » en Haskell, plus un million d’autres trucs) et des concepts haut niveau pour simplifier la programmation (d’accord, les concepts d’Haskell sont très poussés, on est tous d’accord à dessus).

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 4.

                      As-tu déjà envisagé d'étudier OCaml ? Il est de la même famille que Haskell : lambda-calcul fortement et statiquement typé, mais contrairement à lui il n'est pas pur : on peut y pratiquer les paradigmes impératif, fonctionnel et orienté objet.

                      On peut y faire de la programmation système, et même faire des OS.

                      MirageOS is a library operating system that constructs unikernels for secure, high-performance network applications across a variety of cloud computing and mobile platforms. Code can be developed on a normal OS such as Linux or MacOS X, and then compiled into a fully-standalone, specialised unikernel that runs under the Xen hypervisor.

                      De ce que j'en ai compris, un des intérêts des unikernels est de faire de l'isolation de service et de réduire la surface d'attaque. À l'arrivée il n'y a pas à proprement parler de user-land, le noyau et l'unique application (serveur web, serveur mail, serveur de minage bitcoin…) sont fondus en un tout unique. D'où la notion de library operating system : selon l'application à la compilation on ne prend que les bibliothèques strictement nécessaire tant du noyau proprement dit que de l'application.

                      d’accord, les concepts d’Haskell sont très poussés, on est tous d’accord à dessus

                      Non, on n'est pas tous d'accord là-dessus. Bien souvent ce sont des concepts simples à expliquer mais les haskelleux sont pédants dans leur manière de s'exprimer et ont systématiquement recours au vocabulaire de la théorie des catégories au lieu de vulgariser leur propos.

                      J'en avais fait un commentaire humoristique en illustrant les notions de catamorphisme, anamorphisme et hylomorphisme… sur l'algorithme de la multiplication tel qu'on l'enseigne à des enfants de 8 ans !

                      Dans le même genre il y a ce journal (désolé Aluminium 95 ;-) où un docteur en physique, et ce n'est pas le seul, se plaignait de l'usage d'un jargon incompréhensible. Je m'y suis collé pour vulgariser le propos. En gros le problème était : comment encoder des arbres dans un langage fonctionnel dont le système de type ne dispose pas de types récursifs, et cela afin de faire un langage embarqué (EDSL) ?

                      Pour ce qui est d'ailleurs du vocabulaire et des concepts utilisés en Haskell, l'abus qu'ils en font ne fait pas toujours plaisir aux spécialistes du sujet.

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 0.

                        Haskell permet de faire lui aussi des unikernels, mais j’ai l’impression qu’il y a un problème derrière ça : on réutilise énormément de code pour produire un simple binaire. Je trouve que cela va à l’encontre de la simplicité, et je ne sais pas ce que ça pourrait donner sur des gros programmes. La philosophie Unix, moi j’aime bien, et ça la casse complètement, pour un gain pas très clair. Certes tu vas plus vite qu’en ayant toute une machine virtuelle qui tourne, m’enfin le problème ne viendrait-il pas d’ailleurs ? Il me semble concevable de juste produire un binaire et de le faire exécuter sur une machine distante sans avoir à gérer toute une VM pour ça. Le fait que nous utilisions des VM n’est qu’une question de simplicité de déploiement : on fait n’importe-quelle application, on aura du stockage, on aura du réseau. Je pense qu’il y a une autre façon de voir les choses, et je vous en parlerai bientôt.

                        D’accord pour dire que la vulgarisation c’est compliqué. J’en ai bien conscience, et apprendre le Haskell ce n’est pas simple en partie à cause d’explications absconses. Je ne voulais dire que ça dans mon premier commentaire.

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 1. Dernière modification le 05/10/16 à 12:40.

                          Il me semble concevable de juste produire un binaire et de le faire exécuter sur une machine distante sans avoir à gérer toute une VM pour ça.

                          De ce que j'ai compris, l'idée de la VM c'est pour l'isolation et la sécurité : si il y a une faille cela ne compromet pas l'hôte, là où une faille dans un binaire peut aller jusqu'à donner un accès root à la machine distante qui, comme elle aura un noyau complet et un user-land, offrirait des possibilités illimités à l'attaquant. Avec un unikernel, même s'il y a une faille l'attaquant ne pourra jamais en tirer partie ou très peu.

                          D’accord pour dire que la vulgarisation c’est compliqué. J’en ai bien conscience, et apprendre le Haskell ce n’est pas simple en partie à cause d’explications absconses.

                          Je suis bien d'accord avec toi sur la difficulté de la vulgarisation, je m'améliore avec les années mais j'ai encore de gros progrès à faire sur ce plan. Néanmoins, je maintiens que pour apprendre la programmation fonctionnelle il n'est pas nécessaire de passer par ces explications absconses. Cela peut s'avérer nécessaire si l'on veut faire des choses très poussées et évoluées1 — comme avec le C++ de ce que j'ai compris de la discussion de ce fil ;-) — mais pour apprendre on n'en a pas besoin.

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 3.

                            De ce que j'ai compris, l'idée de la VM c'est pour l'isolation et la sécurité : si il y a une faille cela ne compromet pas l'hôte, là où une faille dans un binaire peut aller jusqu'à donner un accès root à la machine distante qui, comme elle aura un noyau complet et un user-land, offrirait des possibilités illimités à l'attaquant. Avec un unikernel, même s'il y a une faille l'attaquant ne pourra jamais en tirer partie ou très peu.

                            Techniquement, on a déjà vu des faille de sécurité d'hyperviseur et les OS commencent depuis quelques temps (oui parce que seccomp c'est pas vieux, hein ?) à avoir ce qu'il faut pour sandboxer un binaire. Pour les unikernel, je ne suis pas certain que l'idée soit vraiment la sécurité. Ou si mais pas pour des raison de cloisonnement, plutôt par réduction de la surface d'attaque. L'un des gros intérêt c'est d'avoir quelque chose de petit. Ça te permet de créer des VM dont la taille se compte en Mio. Du coup :

                            • tu diminue la surface d'attaque
                            • tu diminue la taille de ton image face à une image de VM
                            • tu part sur un environnement maîtrisé comme avec un VM
                            • tu peu envisager des optimisations profondes (pas de recopie de données userland/kernelland, moins de protection à avoir, inlining poussé, etc)

                            Ça ne marche pas pour tout, mais c'est une technique qui peut avoir du sens je trouve.

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

                            • [^] # Re: Donc pour résumer…

                              Posté par . Évalué à 0.

                              Merci pour ces précisions. Comme je l'ai dit c'est ce que j'en avais compris, et ne connaissant pas vraiment le sujet et les problématiques c'était à prendre avec des pincettes. Mais l'idée reste bien de n'embarquer que le strict nécessaire au niveau des fonctionnalités du noyau et de l'application pour réduire la surface d'attaque ? Et cela avec la sécurité des systèmes de typage des langages fonctionnels, ce qui réduit également le risque de failles ?

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

                              • [^] # Re: Donc pour résumer…

                                Posté par . Évalué à 3.

                                Mais l'idée reste bien de n'embarquer que le strict nécessaire au niveau des fonctionnalités du noyau et de l'application pour réduire la surface d'attaque ?

                                C'est aussi pour l'alléger pour un démarrage plus rapide par exemple.

                                Et cela avec la sécurité des systèmes de typage des langages fonctionnels, ce qui réduit également le risque de failles ?

                                Quand c'est dans un langage fonctionnel peut être, mais il en existe en C ou en C++ par exemple :)

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

                                • [^] # Re: Donc pour résumer…

                                  Posté par . Évalué à 2.

                                  C'est aussi pour l'alléger pour un démarrage plus rapide par exemple.

                                  J'avais lu un article de blog, que je n'ai pas retrouvé, où l'auteur avait poussé l'expérimentation jusqu'à servir un site web statique avec une VM par page. :-P

                                  Quand c'est dans un langage fonctionnel peut être, mais il en existe en C ou en C++ par exemple :)

                                  Oh, c'est dommage ! :-P

                                  Dans le genre sécurité et fonctionnement dans la vie réel, il y a eu un conteste de sécurité avec une prime de 10 bitcoin pour qui casserait la pinãta. Personne n'a réussi, voilà le compte-rendu :

                                  TL;DR: Nobody took our BTC. Random people from the Internet even donated into our BTC wallet. We showed the feasibility of a transparent self-service bounty. In the style of Dijkstra: security bounties can be a very effective way to show the presence of vulnerabilities, but they are hopelessly inadequate for showing their absence.

                                  What are you talking about?

                                  Earlier this year, we released a Bitcoin Piñata. The Piñata was a security bounty containing 10 BTC and it's been online since 10th February 2015. Upon successful mutual authentication, where the Piñata has only a single trust anchor, it sends the private key to the Bitcoin address.

                                  It is open source, and exposes both the client and server side of ocaml-tls, running as an 8.2MB MirageOS unikernel. You can see the code manifest to find out which libraries are involved. We put this online and invited people to attack it.

                                  Any approach was permitted in attacking the Piñata: the host system, the MirageOS TCP/IP stack, our TLS, X.509 and ASN.1 implementations, as well as the Piñata code. A successful attacker could do whatever they want with the BTC, no questions asked (though we would notice the transaction).

                                  Lire la suite…

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

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 3.

                          La philosophie Unix, moi j’aime bien, et ça la casse complètement, pour un gain pas très clair.

                          Pas vraiment non. Les gens qui utilisent des unikernel, le font pour avoir un OS qui fait une seule chose, mais qui le fait bien et découpe leur programme en une série de services chacun ayant une responsabilité propre et bien limité. Oui c'est fait pour du microservice.

                          VM n’est qu’une question de simplicité de déploiement

                          Pas que. Déjà la simplicité de déploiement sous linux, on est les dieux des déploiement complexes (on a juste un milliard de façon « simples » de faire des déploiements qui sont juste pas tous compatible avec la distribution que tu vise…). D'autre part le sandboxing est assez récent. SElinux est vieux, mais c'est rare de voir des softs qui ont leurs règles associés et seccomp c'est très récent.

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 0.

                            Tu te retrouves avec des programmes qui ont des milliers de fonctionnalités, avant même d’avoir codé quoi que ce soit, ce n’est pas ce que j’appelle de la simplicité. Réduire une surface d’attaque, on sait déjà très bien faire : tu utilises un service simple, avec le moins de fonctionnalités possibles pour vérifier correctement les entrées. Sur un système, tu vas pas avoir des centaines de programmes qui vont écouter sur ton réseau et qui, par définition, seraient des failles potentielles.

                            Bref, j’attends de voir ce que ça va donner dans la vraie vie.

                            • [^] # Re: Donc pour résumer…

                              Posté par . Évalué à 3.

                              Ton programme en unikernel n'a aucune fonctionnalité tant qu'il ne les pas créé lui-même (tu ne paie que ce que tu utilise). Il est équivalent à un système totalement épuré comme tu le dis (mais en plus simple - pas d'init, pas de configuration particulière, etc). Mais la plupart des gens (et des images que tu trouve sur amazon/azure/google sont des systèmes complets comme des RedHat, des ubuntu ou des Debian).

                              Bref, j’attends de voir ce que ça va donner dans la vraie vie.

                              Ça n'est pas nouveau, hein ? Ça existe depuis pas mal de temps sur des archi ASMP ou NUMA où l'on pose un unikernel par CPU. Pour des services ça existe depuis quelques temps, même si c'est à la mode que depuis quelques années.

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

                              • [^] # Re: Donc pour résumer…

                                Posté par . Évalué à 0.

                                Pour des applications assez spécifiques alors, non ? Parce que pour mes usages courants (DHCP, DNS, web…) je n’ai rien de tout ça sur mes ordinateurs.

          • [^] # Re: Donc pour résumer…

            Posté par . Évalué à 5.

            c'est à dire ? Si tu fais référence aux accès mémoires invalides, il est facile aujourd'hui d'écrire du code qui ne génère pas plus de segfault que de NULLPointerException.

            Je voulais parler des messages d'erreurs, mais je ne me suis rendu compte de l'ambiguïté après avoir validé. Par contre, je rebondis sur la prétendue facilité : quand définir un membre statique (encore un exemple de syntaxe absconse d'ailleurs) au mauvais endroit fait segfault le programme totalement ailleurs, ce n'est pas facile d'écrire du code sans segfault. Par ailleurs, il y a plein de facilités pour écrire du code qui segfault (arithmétique des pointeurs, casts à l'arrache, padding des structs, pas de garantie de la durée de vie des objets, …).

            je ne rentre pas çà dans les lacunes très importantes ; de plus encore une fois pour avoir vu de l'environnement Java complexe

            Ce n'est pas parce que c'est un potentiel défaut de Java (je ne connais pas assez le langage pour en juger) que ce n'en est pas un pour C++.

            j'ai pas souvenir de préprocesseur qui soit meilleur ailleurs, quand il y a ; qu'est-ce qui te manque et qui existe ailleurs ?

            Il devrait simplement disparaître. Les #include c'était bien en 1960, mais on fait mieux maintenant (même Turbo Pascal avait déjà un système de modules) et les macros sont un cauchemar de lisibilité et de maintenance, sans oublier qu'elles pourrissent le scope global.

            je partage ton sentiment mais est-ce réellement un problème ? Le templating pose problème aux gens utilisant vraiment le template (les boss quoi), car pour les cas les plus simple j'ai le sentiment que c'est pas plus compliqué que Java pour la très grande des développeurs

            Premièrement, Java n'a AFAIK que les génériques et pas le côté méta-programmation, ce n'est donc pas comparable. Ensuite, l'ordre et la nécessité des mots-clefs sont imbitables et les erreur souvent incompréhensibles (cf. l'exemple du post d'ailleurs). Enfin, je doute fortement qu'il faille être un « boss » pour utiliser des génériques (ou alors je suis un dieu de la programmation qui s'ignore :p).

            il n'a qu'à utiliser les smart pointer ; non ?

            Non, parce que l'ABI des smart pointers n'est pas définie ; donc l'interopérabilité du code n'est garantie que pour des libs headers-only.

            la question est plutôt qu'est-ce qui devrait être là aujourd'hui ?

            Récupérer boost en dépendance pour pouvoir gérer le FS correctement, c'est tout de même un peu fort ; d'autant plus quand on trouve dans la stdlib des trucs complètement chiadés, mais rien de portable pour récupérer le $HOME. Ensuite, si la culture c'est de récupérer des libs externes pour faire des trucs de base, alors qu'il y ait un gestionnaire de paquets correct (comme pip/gem/cargo/…). On en a vu défiler une kyrielle ces dernières années, mais aucun n'est resté.

            pas d'accord car c'est surtout une manière de coder

            Pas d'accord, parce que soit on a une stdlib simple et on rajoute plein de trucs en dépendances (comme Rust), mais on se débrouille pour que rajouter des dépendances ne soit pas infernal ; soit un a une stdlib bien remplie (comme python, et pourtant il a pip) et on pense à introduire une fonction pour enlever un élément d'un vecteur avant le générateur de gaussiennes.

            pour quelqu'un codant en C++ c'est pas gênant ; je vois pas où est le problème

            Parce que ça implique de maintenir plein de choses dans le langage juste pour conserver cette compatibilité, ce qui freine l'évolution du standard.

            Je ne vois rien là dedans de catastrophique

            Tant mieux pour toi, et je suis sincèrement ravi que tu aies trouvé un langage qui te convienne. Toutefois, je me porte bien mieux depuis que j'ai remplacé C++ par Rust dans ma boîte à outils (instant pub, je conseille à tous les C++-eux d'y jeter un coup d'oeil).

            et il me semble qu'ils y travaillent

            Depuis 2011, on a réussi à avoir Go, Rust, Swift, Elixir, Nim et sûrement plein d'autres que j'oublie. En C++, on a toujours pas de librairies pour le système de fichiers. Donc s'ils y travaillent, soit ils prennent beaucoup de temps, soit ils n'en ont rien à carrer, soit il passent leur temps à tirer la couverture à eux (de ce que j'ai pu lire par-ci par-là, cette dernière hypothèse serait la plus probable).

            le C++ n'a pas vocation à remplacer/dominer tous les autres langages.

            Ce n'est pas une question de dominer qui que ce soit ou de savoir qui a la plus grosse qui pisse plus loin, c'est une question de fournir un environnement de travail agréable à l'utilisateur. Et pour ça, C++ ne part pas gagnant.

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 2.

              Go, Rust, Swift, Elixir, Nim

              Vous parlez pas mal de ces langages mais est-il possible de faire de l'interface graphique avec des derniers (swift mis à part puisque c'est le "remplaçant" d'objective-C) ?
              Et dans ce cas quelles sont les librairies graphiques "par défaut" de ces langages ?

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 2.

                GTK a des bindings C, donc tout le monde peut l'utiliser.

                Ensuite, un GUI c'est large comme domaine. On parle des GUI natives à la GTK/Qt, des GUI OpenGl, de l'interfaçage avec du HTML/CSS/JS, … ?

                Je ne travaille qu'avec Rust, mais je n'ai pas eu pour le moment besoin de GUI. Et si je devais en faire une, je pense que je m'en ferais une en Java/Kotlin/C# interfacé avec le backend Rust.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  Je ne travaille qu'avec Rust, mais je n'ai pas eu pour le moment besoin de GUI. Et si je devais en faire une, je pense que je m'en ferais une en Java/Kotlin/C# interfacé avec le backend Rust.

                  Intéressant.
                  Donc ce n'est pas un problème si C++ ne gère pas les GUI, puisqu'on peut aussi interfacer avec du "Java/Kotlin/C#" (même s'il faut passer par du C++ Céifié à cause de l'ABI, mais c'est un autre sujet que l'absence de GUI, et j'ai beau aimer le C++ je sais que le manque d'ABI stable est un véritable problème. Enfin, je ne sais pas comment ça se fait en Rust, non plus).

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 0.

                    Donc ce n'est pas un problème si C++ ne gère pas les GUI, puisqu'on peut aussi interfacer avec du "Java/Kotlin/C#"

                    C'est peut-être de la pure paresse de ma part, mais j'avoue qu'utiliser un langage relativement bas niveau pour le GUI me gonfle :)
                    C++ a Qt qui est extrêmement bien fichu donc je l'utilise régulièrement, mais j'ai de plus en plus tendance à passer à Python+PyQt pour les GUI et interfacer avec un backend à plus hautes performances.

                    même s'il faut passer par du C++/Céifié

                    On ne pourra pas passer par du C++ (ou n'importe quel autre langage qui utilise autre chose que pointeurs + types machines) tant qu'une ABI n'aura pas été définie ; ce n'est même pas une question de stabilité, c'est une question d'inexistence :/

                    En ce qui concerne Rust, il a une FFI plutôt correcte avec le C (avec les mêmes limitations que les autres langages), même si des gens ont commencé à profiter des capacités de méta-programmation pour faire des trucs kikoo.

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 2.

                      mais j'ai de plus en plus tendance à passer à Python+PyQt pour les GUI et interfacer avec un backend à plus hautes performances.

                      Je comprend.

                      On ne pourra pas passer par du C++ (ou n'importe quel autre langage qui utilise autre chose que pointeurs + types machines) tant qu'une ABI n'aura pas été définie ;

                      Sauf que ce bout de code:

                      struct foo
                      {
                          int* ptr;
                      };
                      
                      foo foo_make( void )
                      {
                          foo f; f.ptr = 0;
                          return f;
                      }

                      C'est du C++ parfaitement valide. L'ABI est stable, bien que j'aie un doute sur le fait que ce soit par convention ou par standard.
                      Le fait est que, bien sûr, il faut se débarasser de la totalité des fonctionnalités C++ dans l'API, mais bon… je pinaille, j'avoue :)
                      En tout cas, j'en retire que ce que tu fais avec python+rust, tu peux le faire avec python+C++.

                      En ce qui concerne Rust, il a une FFI plutôt correcte avec le C

                      Je ne suis pas expert, mais j'ai l'impression que c'est un peu la meme chose partout, de ce point de vue la. Enfin, non, puisqu'en C++, justement, il n'y a pas besoin d'autant de colle.
                      Objectivement, je dirais avantage à C++ en terme de quantité de code vital pour importer du code C, et pour cause, un sous-ensemble du C++ est entièrement compatible avec le C (mais pas l'inverse, parce qu'il me semble que le C++ réserve certains mots non réservés par le C, genre public).

                      Enfin, ce genre de trucs me rappelle la fois ou j'avais du interfacer du C++ avec powerbuilder12… j'avais fini par pondre un wrapper complet, qui permettait avec quelques macros (non nécessaires elles, mais ça réduisait méchamment le boilerplate) et beaucoup de template, histoire d'etre sur que quand ça plantait ça venait pas de la colle powerbuilder/C++… foutu langage proprio de merde, si tu veux mon avis.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 1. Dernière modification le 02/10/16 à 21:30.

                Je ne vois pas l’intérêt d’avoir des bibliothèques dédiées à faire des GUI dans tous les langages. Un binding C (ou autre) vers des bibliothèques déjà bien (trop ?) fournies en matière de GUI suffit.

                Nul besoin de réinventer la roue à chaque nouvelle façon d’écrire du code.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  "Un binding C (ou autre) vers des bibliothèques déjà bien (trop ?) fournies en matière de GUI suffit."

                  En théorie oui, en pratique pas du tout.

                  Faire de l'objet en C, est verbeux et moche, c'est pour ça, que Qt est bien plus utilisé en C++ que gtk.

                  Ocaml dispose d'un wrapper gtk… mais l'interface aurait été tellement plus simple avec des arbres un peu abstrait qu'une série d'objet à manipuler.

                  En gros, un wrapping implique l'usage d'un plus petit dénominateur commun des langages. Donc, on utilise des techno simpliste pour les bibliothèques les plus complexe d'un langage…

                  "La première sécurité est la liberté"

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 0.

                  Je ne vois pas l’intérêt d’avoir des bibliothèques dédiées à faire des GUI dans tous les langages. Un binding C (ou autre) vers des bibliothèques déjà bien (trop ?) fournies en matière de GUI suffit.

                  Nul besoin de réinventer la roue à chaque nouvelle façon d’écrire du code.

                  Ça dépend si la vitesse à laquelle tu écris le code et l'énergie que tu déploies à le débuguer ont de l'importance pour toi…

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à -2.

              Je voulais parler des messages d'erreurs, mais je ne me suis rendu compte de l'ambiguïté après avoir validé.

              Ça c'est partout pareil (à part les templates encore une fois) ; quand on a l'habitude du langage, les messages sont clairs, quand on n'en a pas l'habitude, on galère et on cherche.

              Par contre, je rebondis sur la prétendue facilité : quand définir un membre statique (encore un exemple de syntaxe absconse d'ailleurs) au mauvais endroit fait segfault le programme totalement ailleurs, ce n'est pas facile d'écrire du code sans segfault.

              C'est quoi le mauvais endroit ? J'ai jamais eu de soucis, mais peut-être ai-je les bonnes pratiques ?

              Par ailleurs, il y a plein de facilités pour écrire du code qui segfault (arithmétique des pointeurs, casts à l'arrache, padding des structs, pas de garantie de la durée de vie des objets, …).

              Même propos qu'avant : c'est pas parce que c'est là, qu'on doit utilise. Si on comprend pas, n'utilise pas, n'a pas besoin, alors pourquoi y jeter un oeil ?
              Ça veut dire quoi « pas de garantie de la durée de vie des objets » ?

              Il devrait simplement disparaître. Les #include c'était bien en 1960

              Même topo : en cas c'est gênant ; il suffit de pas utiliser.

              Non, parce que l'ABI des smart pointers n'est pas définie ; donc l'interopérabilité du code n'est garantie que pour des libs headers-only.

              Encore une fois, il s'agit d'un choix de librairies.

              Récupérer boost en dépendance pour pouvoir gérer le FS correctement, c'est tout de même un peu fort
              et
              pas d'accord car c'est surtout une manière de coder

              Non question de philosophie de langage.
              Pour ton problème de suppression de case (vient de SO):
              vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());

              Un tableau n'est pas une liste, il est logique qu'il faille un certain nombre de manipulation pour retirer une case. Après si on désire faire des choses plus facilement, pourquoi pas la QList ? C'est à dire utiliser d'autres librairies.

              Parce que ça implique de maintenir plein de choses dans le langage juste pour conserver cette compatibilité, ce qui freine l'évolution du standard.

              Franchement ça me concerne pas en tant que développeur.

              Pour la suite je ne parle pas particulièrement de toi.
              Quand je vois la teneur des reproches (car je sens plus de la colère qu'autre chose, et on voit les notes dès que l'on soutient le C++), je pense que c'est surtout idéologique.
              En gros on reproche :
              - des trucs trop compliqués à utiliser, et que l'on se force à utiliser
              - des trucs pas nécessaires et qu'on a pas à utiliser, et que l'on se force à utiliser
              - le fait d'avoir à utiliser des librairies (on verra dans quelques temps les « Go, Rust, Swift, Elixir, Nim » quand ils commenceront à avoir des librairies à gauche et à droite, qu'il y aura les problèmes de versionement, etc ; pensez à python, javascript qui étaient vachement mieux que tout).
              - c'est lent à compiler ; les entêtes pré-compilés ?
              - les templates : ok, on sait, tout le monde le sait, le dit, le pense

              Non vraiment il y a des trucs chiants en C++ (ok il y a les TEMPLATES, et beaucoup d'autres choses qui bizarrement n'apparaissent pas), mais personne n'oblige personne à utiliser quoique ce soit.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 5.

                Donc le langage n’offre pas toujours quelque-chose de simple à utiliser pour certains usages, mais c’est pas grave : il suffit de trouver la bibliothèque qui sera simple à utiliser. Le langage laisse faire des opérations qui peuvent segfault, mais suffit de pas les utiliser (…).

                J’ai du mal avec ce raisonnement. Le langage devrait aider le développeur à éviter les erreurs à l’exécution, et proposer des abstractions simples pour les choses simples (le réseau et le système de fichiers, c’est la base).

                Aussi, les fonctionnalités dont tu parles et qu’il serait bon selon toi de ne pas y jeter un œil, en vrai on en a besoin pour faire du code propre. C’est bien là le problème. Faire du baby-scripting pour ne pas se taper des maux de tête ça va si c’est pour du code jetable ou du code court (ce qui est assez compliqué à avoir avec du C++), mais dès qu’on veut faire du code propre il faut savoir utiliser les abstractions du langage. Si ces abstractions sont complexes et verbeuses, il y a un problème non pas avec le développeur, mais bel et bien avec le langage.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à -8.

                  Je fatigue, et le ton me fatigue.

                  Quand j'ai le choix j'utilise le langage que je trouve adapté à la situation. Il se trouve que quelques soient les langages que j'ai utilisé je n'ai pas fait des choses triviales (Java, Php, C++, Javascript, etc), et j'ai toujours eu des problèmes avec tous ces langages car ils ne gèrent pas ci, pas ça, ils sont pourris à débugger, whatsoever. Quelques soient les langages il a fallu que je me paluche des librairies, des bindings à faire à la mimine, etc. Et il y a des choses super dans tous les langages que j'aimerais retrouver partout (Lisp, prolog, etc).
                  Mon code C++ est bourré de nouveautés, j'adore les lambdas, les templates, je développe multi-plate-forme : donc je pense que j'ai vu plein de trucs chiants, compliqués, et je pense que je ne fait pas non plus de code trivial car je prends le temps de réfléchir et de re-faire sur mon temps perso car j'aime développer.

                  Ceci dit, je ne pense pas que la majorité des développeurs, tout langage confondu, fasse du code compliqué, et qu'il suffit donc à cette majorité d'utiliser ce dont elle a besoin et ce qui est safe ; voilà tout.

                  Peut-être suis-je trop souple et que je cherche à retirer ce qui est bon de ce que j'utilise, j'en sais rien.

                  En conclusion si le langage vous fait chier, alors ne l'utilisez pas, changez d'entreprise, changez de métier ou suicidez-vous. Il faut jamais rester dans une situation de souffrance.

                  Ciao.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 3.

                Pour la suite je ne parle pas particulièrement de toi.
                Quand je vois la teneur des reproches (car je sens plus de la colère qu'autre chose, et on voit les notes dès que l'on soutient le C++), je pense que c'est surtout idéologique.
                En gros on reproche :

                • des trucs trop compliqués à utiliser, et que l'on se force à utiliser
                • des trucs pas nécessaires et qu'on a pas à utiliser, et que l'on se force à utiliser
                • le fait d'avoir à utiliser des librairies (on verra dans quelques temps les « Go, Rust, Swift, Elixir, Nim » quand ils commenceront à avoir des librairies à gauche et à droite, qu'il y aura les problèmes de versionement, etc ; pensez à python, javascript qui étaient vachement mieux que tout).
                • c'est lent à compiler ; les entêtes pré-compilés ?
                • les templates : ok, on sait, tout le monde le sait, le dit, le pense

                Le C++ est le seul langage que je connaisse avec des parties « partiellement dépréciés par usage », c'est à dire qu'il y a des parties du standard que les développeurs considèrent qu'il ne faut plus utiliser (sauf dans certains cas ou si on est suffisamment bon). Bref l'usage veut que l'on affine le langage pour son projet à fin de le rendre exploitable.

                Le problème c'est que je n'ai jamais vu ces bonnes pratiques décrites de manière un minimum formelle et qu'il faut composer avec l'existant. Si ton logiciel commence son développement en 2018 pas de problème pour faire du C++17 avec un compilateur qui implémente à peu près tout le standard, mais globalement je pense que beaucoup de monde joue avec des sources qui sont en C++03, donc au revoir les smart-pointers.

                C'est assez dommageable à un langage qui tente de se simplifier, mais qui le fait uniquement en ajoutant des couches de simples par dessus des couches complexes. On a accès aussi facilement à n'importe quelle façon et il faut avoir un bon niveau pour connaître la bonne méthode (il faut être bien à jour sur sa connaissance du standard).

                • le fait d'avoir à utiliser des librairies (on verra dans quelques temps les « Go, Rust, Swift, Elixir, Nim » quand ils commenceront à avoir des librairies à gauche et à droite, qu'il y aura les problèmes de versionement, etc ; pensez à python, javascript qui étaient vachement mieux que tout).

                Non c'est un peu plus compliqué que ça. La définition de ce qui est ou non dans une bibliothèque standard est un problème via comme la programmation et il y a plusieurs démarches possibles qui sont aussi liées aux propriétés du langages (par exemple en fonction de son typage). Ici ce qu'il est reproché c'est d'être très poussé pour des usages pointues et d'avoir des lacunes pour des usages très généraliste. Si je crée mon langage BarmicML avec tous les algo machine learning dans la bibliothèque standard, mais absolument rien pour faire des tableaux, listes, map, dictionnaires, set ou autres collections ça serait bizarre, non ?

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

                • [^] # Re: Donc pour résumer…

                  Posté par (page perso) . Évalué à 5.

                  mais globalement je pense que beaucoup de monde joue avec des sources qui sont en C++03, donc au revoir les smart-pointers.

                  En tant que contributeur au projet Haiku, qui est écrit en C++98 et compile avec gcc 2.95.3 pour diverses raisons, je ne suis pas d'accord. On a implémenté nos propres smart pointers et on les utilise là ou c'est nécessaire.

                  Le C++ a cet intérêt pour nous qu'il est un des rares langages avec lesquels on peut écrire un système d'exploitation entier: noyau, drivers, interface graphique, applications, etc. Et pour y arriver, on a besoin de la plupart des fonctionnalités de C++. Certains trucs "pas propres" dans une application sont nécessaires pour la programmation bas niveau (par exemple, on a besoin de pointer sur une adresse mémoire "en dur" pour accéder à du matériel). Donc, il y a des morceaux du C++ qui servent rarement, mais qui sont nécessaires dans quelques cas, et c'est bien qu'elles soient disponibles.

                  S'il ne devait rester qu'un seul langage, C++ serait le mieux placé. Par contre, si on préfère en avoir 2 ou 3, alors oui, faire du C ou un autre truc spécialisé dans le bas niveau côté noyau, et un langage objet "moderne" pour les applications, ça pourrait marcher.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 3.

                    En tant que contributeur au projet Haiku, qui est écrit en C++98 et compile avec gcc 2.95.3 pour diverses raisons, je ne suis pas d'accord. On a implémenté nos propres smart pointers et on les utilise là ou c'est nécessaire.

                    Et je peux faire tout mon code java off the heap et faire ma gestion mémoire manuelle en java (oui c'est utilisé dans du vrai code dans la vraie vie en production tout ça tout ça).

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

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 0.

                    Tu t'occupes de développer un système d'exploitation, et visiblement tu le fais en C++. Du coup, je suis très intéressé par avoir un retour de ta part du développement d'un système avec Rust, comme dans le cas de redox os. Si un jour tu as le temps de t'y pencher un peu… n'hésite pas. :)

                    • [^] # Re: Donc pour résumer…

                      Posté par (page perso) . Évalué à 0.

                      Pas trop le temps, et le Rust reste encore assez illisible pour moi (mais j'ai sûrement trop l'habitude du C++).

                      Quand je vois des accès au module VBE (pour la gestion des graphismes en VESA) dans le driver de souris PS/2 (https://github.com/redox-os/redox/blob/master/kernel/drivers/ps2.rs), je m'interroge sur la conception objet et l'encapsulation. Mais le projet est tout récent et semble plutôt bien avancer, on verra où ils en sont dans 15 ans (c'est l'âge de Haiku) ou dans 25 (c'est l'âge de Linux).

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 0.

                    Je n'ai jamais écris une ligne de C++. Donc mon impression n'est qu'issus de lectures mais j'ai l'impression que le C++ est très vaste et permet d'écrire avec des styles très différents.

                    La question est donc de savoir qu'est-ce qui est le plus simple: C++ en entier ou l'union de 3 langages nettement plus simple et des bindings correspondant ?

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 0.

                      Il n’y a pas nécessairement besoin de « bindings », on peut simplement faire des binaires différents, suivant le besoin. Je vois difficilement le C++ remplacer le Perl pour le parsing de texte par exemple, et si quelqu’un veut s’y coller je pense que ça donnera lieu à des lignes de code vraiment pénibles à taper, mémoriser, utiliser.

                      Apprendre un langage pour s’en servir que pour une tâche bien précise, ça peut donner lieu à un apprentissage assez court car très orienté. Je pense qu’en définitive tout est plus simple en fonctionnant de cette manière : le langage est dédié à une tâche, tu produis un binaire éventuellement réutilisable par d’autres programmes (on évite le plus possible de s’amuser avec des bindings), tu le debug facilement vu que le langage est simple, car il n’a pas besoin d’implémenter absolument tout et n’importe-quoi, juste ce pour quoi il est fait…

                      Si on suit la philosophie UNIX, un outil = une tâche, j’ai toujours pensé que cela pouvait s’appliquer aux langages de programmation dans une certaine mesure. Et le traitement de chaînes de caractères en est, selon moi, l’exemple le plus édifiant.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2. Dernière modification le 03/10/16 à 14:15.

                  beaucoup de monde joue avec des sources qui sont en C++03, donc au revoir les smart-pointers.

                  Non, au revoir la move semantic, et donc au revoir std::unique_ptr.
                  Mais std::auto_ptr (maintenant déprécié car remplacé par unique_ptr qui est plus sûr, certes) existait déjà en C++03. Et c'était toujours mieux que d'utiliser des raw, mais personne ne l'utilisait. Probablement parce que, par exemple, les gens qui enseignent ne connaissaient pas nécessairement son existance (j'en ai jamais entendu parler à l'école, des pointeurs intelligents… de manière générale, on à très peu parlé de la lib standard, je ne sais vraiment pas pourquoi, ça aurait réduit pas mal certaines conneries).
                  Et pour ce qui est de shared_ptr, je pense que ce truc est une erreur, mais bon, un pointeur intelligent avec compteur de référence ce n'est pas ce qu'il y a de plus difficile à coder non plus.
                  Enfin, tant qu'on reste dans le mono-threadé ou si l'on accepte d'utiliser des libs externes: le multi-thread était inexistant avant C++11 après tout.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 1.

                    Et pour ce qui est de shared_ptr, je pense que ce truc est une erreur

                    C'est à dire que les gars prennent 10 ans pour réfléchir à des fonctionnalités basiques du langage (basique dans le sens utilisation quotidienne dans tous les programmes, pas un cas tordu qui n'arrive que quand tu fais du développement noyau ou je ne sais quoi) et ils se prennent ce genres de remarques ?1

                    Il y a pas un problème quelque par ?


                    1. je ne remet pas du tout en cause ta remarque, je ne sais pas si c'est vrai ou pas, mais le fait qu'il y a des gens qui trouvent que des nouvelles fonctionnalités mal réfléchies pose AMHA un vrai problème. 

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

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 0.

                      Il y a pas un problème quelque part ?

                      Souvent, les gens qui critiquent n’ont qu’une connaissance partielle du problème, et ont négligé certains aspects.

                      Et sérieusement, je ne pense pas que ce soit spécifique à C++, dans tous les langages des gens critiquent les choix qui ont été faits.

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 1.

                        Et, dans le cas de ta critique, tu es dans la partie "souvent" ou dans l'autre? :D

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 4.

                      D'une part, j'ai bien précisé: "je pense", je n'ai pas dit "c'est une erreur". La nuance est faible, mais existante: je n'ai jamais prétendu être bon, donc je peux penser des conneries.

                      Ceci étant dit, j'ai beaucoup de mal avec l'idée qu'un objet soit possédé par plusieurs autres à la fois, en terme de maintenance j'imagine mal ça simplifier les choses.

                      D'ailleurs, c'est assez amusant. Je me rappelle qu'au début je trouvais l'idée super. J'ai réfléchi à l'utiliser sans jamais y trouver d'usage mais je me disais juste que l'occasion ne s'était pas présentée. De fil en aiguille j'étais tombé sur un billet de blog ou l'auteur expliquait pourquoi il estimait que l'utiliser ne lui semblait pas une bonne idée. J'ai essayé de répondre à ses points (vu que je n'étais pas d'accord du tout), et, en répondant, je me suis aperçu que tous les arguments que j'avais en faveur du shared_ptr étaient mauvais. Du coup j'ai bien du passer 2H à ne rien répondre.

                      Après, peut-être qu'un jour je tomberai sur un cas d'usage réel, après tout, j'ai bien eu une fois un cas ou l'héritage en diamant m'a été utile (je ne dirai pas que ce cas était super bien ficelé, mais dans la situation c'était ça ou duplication d'une quantité de code assez conséquence comparé à la taille du projet)…

                      Pour conclure, je veux bien un cas d'usage quotidien de shared_ptr. Si je peux être détrompé, tant mieux.

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 1. Dernière modification le 06/10/16 à 11:18.

                        Pour conclure, je veux bien un cas d'usage quotidien de shared_ptr. Si je peux être détrompé, tant mieux.

                        Disclaimer : je n'y connais rien en C++ donc ce que je vais dire repose essentiellement sur ma compréhension de la documentation de shared_ptr.

                        On peut y lire :

                        std::shared_ptr est un pointeur intelligent qui permet le partage d'un objet via un pointeur. Plusieurs instances de shared_ptr peuvent posséder le même objet, et cet objet est détruit dans l'un des cas suivant :

                        • le dernier shared_ptr possédant l'objet est détruit ;

                        • le dernier shared_ptr possédant l'objet est assigné à un autre pointeur via operator=() ou reset().

                        Quand je lis cela, je pense immédiatement, par exemple, à la représentation mémoire des listes en OCaml1. Les listes dans ce langage sont des listes chaînées qui partagent leur queue. Je m'explique.

                        let l = [1; 2; 3];;
                        (* la représentation mémoire de celle-ci est une chaîne :
                        
                        | 1 | -|-->| 2 | -|-->| 3 | -|-->| |
                        
                        *)

                        Chaque élément de la liste contient la valeur et un pointeur vers la queue ou la suite de la liste qui se termine par un « pointeur nul »2. Maintenant si je crée deux listes à partir de celle-ci en rajoutant un élément en tête, elles partageront en mémoire la suite l :

                        let l1 = 4 :: l and l2 = 5 :: l;;
                        (* ce qui en mémoire donne :
                        
                        | 4 | \|
                               \
                                -->| 1 | -|-->| 2 | -|-->| 3 | -|-->| |
                               /
                        | 5 | /|
                        *)

                        Ai-je bien compris le concept de shared_ptr et est-ce une utilisation possible ? Pour ce qui est de la libération mémoire par le GC des listes en OCaml, la règle est équivalente à la première de la doc sus-citée : « le dernier shared_ptr possédant l'objet est détruit ».


                        1. c'est en fait le cas de toutes les structures récursives à base de types somme, comme les arbres. 

                        2. c'est pas tout à fait ça mais c'est pour simplifier, c'est plutôt une valeur constante qui correspond à la liste vide et qui est la même pour toutes les listes quelque soit le type des éléments. 

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

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 2.

                          Disclaimer : je n'y connais rien en C++ donc ce que je vais dire repose essentiellement sur ma compréhension de la documentation de shared_ptr.

                          Je tenterai de garder ça en mémoire, mais en retour, je te demande de tenter de garder en mémoire que je ne connais du OCaml que ce que j'ai cru comprendre au travers des discussions de linuxfr, c'est à dire quasi rien excepté quelques concepts hyper vagues (car je n'ai pas fait l'effort de m'investir). Si je me trompe, je compte sur toi pour pour me le dire. C'est à ça que sert un forum technique en même temps.

                          Ai-je bien compris le concept de shared_ptr

                          Je pense que non, mais je me trompe peut-être.
                          Je pense que la confusion viens du fait que les langages fonctionnels semblent (de ce que j'ai compris) favoriser la copie lors d'écriture et être optimisés pour ça. Ils masquent les mécanismes de bas niveau.

                          Le shared_ptr de c++ n'est rien d'autre qu'une zone mémoire qui est partagée entre plusieurs autres objets, et chacun peut y modifier les données. Jusque la, ça va encore.
                          Les problèmes:

                          • les autres propriétaires du shared_ptr n'ont aucune idée de la taille de la zone allouée.
                          • je ne crois pas qu'il y ait moyen de savoir si les données sont valables dans le cas d'un traitement multi-thread/process, car ce n'est vraiment qu'un fichu stupide pointeur!
                          • en admettant que l'on réussisse à contourner ces problèmes, j'attend encore de voir un code maintenable qui exploite cette fonctionnalité. Les standards décrivent ce que l'on peut faire, pas ce que l'on devrait faire, il est important pour moi de garder un esprit critique, même en étant moins intelligent que les gens qui font les standards.
                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 4.

                            Je pense que la confusion viens du fait que les langages fonctionnels semblent (de ce que j'ai compris) favoriser la copie lors d'écriture et être optimisés pour ça. Ils masquent les mécanismes de bas niveau.

                            Oula non, malheureux ! :-P

                            Du moins tout dépend de ce que tu entends par copie. Ils ne favorisent pas du tout la copie, mais au contraire le partage de structures et cela pour deux raisons :

                            1. garantir l'immutabilité des structures quand on fait du fonctionnel pur;
                            2. économiser l'espace mémoire pour réaliser le premier point.

                            Par contre, effectivement, ils cachent les mécanismes bas niveau de gestion de la mémoire puisqu'il y a un GC (même si en OCaml il y a un module qui expose la représentation mémoire, mais dans ce cas il faut savoir ce que l'on fait comme dans des langages tels C ou C++ avec gestion manuelle). Il peut bien, par moment, y avoir « copie » qui en réalité est une « copie + suppression » (ce qui est en fait un « déplacement ») lorsque le GC passe, mais c'est tout.

                            C'est cet encodage par « partage » de structures que j'avais cru voir dans les shared_ptr. Cela étant, je me demande si ces derniers ne permettent pas de coder le même mécanisme, d'après ce que dit rewind dans ce commentaire :

                            puisqu'en C++, les struct et les class, c'est la même chose (à la différence que dans une struct, la visibilité par défaut est publique tandis que dans une class, la visibilité par défaut est privée).

                            Une liste en OCaml, comme dit précédemment, est une liste chaînée de type LIFO ce qui équivaut la structure C suivante :

                            typedef struct liste
                              {
                                int head;
                                struct liste *tail;
                              } liste ;

                            À la différence de cette structure, les listes OCaml sont polymorphes ce qui n'est pas exprimable en C, mais de ce que j'ai compris devrait être exprimable en C++.

                            En OCaml une telle définition de structure s'écrirait :

                            (* un type polymorphe qui prend pour paramètre un type 'a quelconque *)
                            type 'a liste =
                              (* soit la liste vide pour le cas du pointeur nul en C *)
                              | Nil
                              (* soit un élement de type 'a et une liste de même type *)
                              | Cons of 'a (* head *) * 'a liste (* tail *)

                            Pour montrer qu'il y a bien partage en mémoire et non copie, je vais l'illustrer avec l'utilisation de deux prédicats d'égalité = et ==. Le premier est une égalité structurelle et parcourt les deux structures pour vérifier si elles représentent la même chose, bien qu'occupant des zones mémoires différentes; tandis que le second est une égalité physique et vérifie simplement les adresses mémoires. Le second répond en temps constant, tandis que le premier peut même rentrer dans une boucle infinie et ne jamais terminer.

                            (* deux listes structurellement identiques *)
                            let l = [1; 2] and l' = [1; 2];;
                            val l : int list = [1; 2]
                            val l' : int list = [1; 2]
                            
                            (* elles n'occupent pas la même zone mémoire mais représente la même liste *)
                            l = l';;
                            - : bool = true
                            
                            l == l';;
                            - : bool = false
                            
                            (* maintenant je crée deux liste en ajoutant un élément en tête de l *)
                            let l1 = 3 :: l and l2 = 4 :: l;;
                            val l1 : int list = [3; 1; 2]
                            val l2 : int list = [4; 1; 2]
                            
                            (* elles sont bien différentes à tout point de vue *)
                            l1 = l2;;
                            - : bool = false
                            
                            l1 == l2;;
                            - : bool = false
                            
                            (* mais leurs queues sont identiques à tout point de vue *)
                            List.tl l1;;
                            - : int list = [1; 2]
                            
                            List.tl l1 = List.tl l2;;
                            - : bool = true
                            
                            List.tl l1 == List.tl l2;;
                            - : bool = true
                            
                            (* en revanche si je pars de la liste l' *)
                            let l1' = 3 :: l';;
                            val l1' : int list = [3; 1; 2]
                            
                            l1 = l1';;
                            - : bool = true
                            
                            l1 == l1';;
                            - : bool = false
                            
                            List.tl l1 == List.tl l1';;
                            - : bool = false

                            C'est pour cela que je me demande si les shared_ptr, au lieu d'être vus comme une zone mémoire partagée dans laquelle plusieurs objets peuvent écrire, ne peuvent être utilisés pour définir des structures vérifiant ces conditions :

                            1. garantir l'immutabilité des structures ;
                            2. économiser l'espace mémoire pour réaliser le premier point.

                            L'immutabilité d'une structure est une propriété très intéressante et qui permet de raisonner bien plus facilement sur le code : c'est pour cela qu'en fonctionnel pur elles sont toutes ainsi faites.

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 3. Dernière modification le 09/10/16 à 14:44.

                            J'ai réfléchi depuis l'autre jour sur cette notion de shared_ptr (qui n'est qu'un « fichu stupide pointeur », mais géré d'une manière particulière) et je te propose une autre utilisation possible : s'en servir, effectivement, comme une zone mémoire partagée où tous les possesseurs peuvent écrire dedans pour implémenter une structures de tableaux persistants. :-)

                            Le principe de l'encodage sera écrit en OCaml (ce sera plus simple pour moi, et je laisse la traduction en C++ à toi ou un autre). Avant cela, je vais revenir sur la notion de constructeurs de type dans ce langage pour que tu puisses faire le lien.

                            Quand j'ai présenté la définition du type des listes chaînées, je l'ai écrit ainsi :

                            type 'a liste =
                              | Nil
                              | Cons of 'a * 'a liste

                            Une expression de la forme Cons of ... est en réalité un « pointeur » en lecture seule sur la structure décrite ensuite. On pourrait l'illustrer ainsi :

                            type 'a ptr = Ptr of 'a;;
                            let read (Ptr x) = x;;
                            
                            let i = Ptr 1;;
                            val i : int ptr = Ptr 1
                            
                            read i;;
                            - : int = 1

                            Ceci dit, ce langage propose également la notion de zone mémoire accessible en lecture et écriture avec la notion de référence (mot clé ref).

                            let i = ref 1;;
                            val i : int ref = {contents = 1}
                            
                            (* lecture avec l'opérateur préfixe !*)
                            !i;;
                            - : int = 1
                            
                            (* écriture avec l'opérateur infixe := *)
                            i := 2;;
                            - : unit = () (* le type unit est équivalent à void *)
                            
                            (* nouvelle lecture *)
                            !i;;
                            - : int = 2

                            Avant d'en venir au cœur de ce commentaire, il me faut présenter rapidement une autre structure représentant une zone mémoire accessible en lecture et écriture, commune à tous les langages impératifs : les tableaux (ou array).

                            let tab = [|0; 1; 2; 3|];;
                            val tab : int array = [|0; 1; 2; 3|]
                            
                            Array.get tab 0;;
                            - : int = 0
                            
                            Array.set tab 0 4;;
                            - : unit = ()
                            
                            Array.get tab 0;;
                            - : int = 4
                            
                            (* on peut faire la même chose avec cette notation *)
                            tab.(0) <- 5;;
                            - : unit = ()
                            
                            tab.(0);;
                            - : int = 5
                            
                            tab;;
                            - : int array = [|5; 1; 2; 3|]

                            Ces présentations préliminaires étant faites, venons en à l'objectif principal : coder une structure de tableaux persistants en recourant à une zone mémoire accessible en lecture-écriture commune à chacun d'eux. On pourrait faire cela en recopiant le tableau à chaque écriture puis en renvoyant la copie modifiée, mais ce serait vite coûteux en mémoire. L'idée pour obtenir la persistance à moindre coût va être celle qui est derrière les gestionnaires de versions (git, svn, mercurial…) : une écriture sera vu comme un patch et on conservera en mémoire la structure des diff.

                            On commence par définir le type polymorphe 'a t de nos tableaux :

                            type 'a t = 'a data ref
                            and 'a data = |Arr of 'a array 
                                          |Diff of int * 'a * 'a t

                            On a deux types mutuellement récursifs : le type 'a data qui pointe en lecture soit sur un tableau, soit sur un triplet correspondant à une écriture atomique sur un tableau, qui lui est un pointeur en lecture-écriture sur une structure 'a data.

                            Tout le code sur cette structure va reposer sur la fonction reroot qui modifie le graphe des pointeurs afin de toujours rentrer dans la structure par un pointeur en lecture sur un tableau 'a array. Autrement dit : on applique tous les patchs sur le tableau d'origine pour présenter le tableau résultant.

                            let rec reroot pa = match !pa with
                              |Arr a -> a
                              |Diff (i, v, pa') ->
                                 (* pa est vu comme un diff de pa', on commence par rerooter sur lui *)
                                 let a = reroot pa' in
                            
                                 (* on applique le patch atomique *)
                                 let old = a.(i) in
                                 a.(i) <- v;
                                 pa := Arr a;
                            
                                 (* maintenant c'est pa' qui est vu comme un diff de pa *)
                                 pa' := Diff (i, old, pa);
                            
                                 (* on retourne le « véritable » tableau correspondant à pa *)
                                 a

                            À partir de cette fonction utilitaire, on peut définir les fonctions usuelles sur les tableaux à partir de celles sur les array.

                            let length pa = Array.length (reroot pa)
                            
                            let get pa i = (reroot pa).(i)
                            
                            let iter f pa = Array.iter f (reroot pa)
                            
                            (* ici la fonction f que l'on itère prend deux paramètres : l'indice et la valeur *)
                            let iteri f pa = Array.iteri f (reroot pa)

                            Pour la fonction set, il faut prendre soin de rerooter sur le tableau que l'on veut patcher puis rajouter le diff dans la structure :

                            let set pa i v =
                              let a = reroot pa in
                              let old = a.(i) in
                              a.(i) <- v;
                              let res = ref (Arr a) in
                              pa := Diff (i, old, res);
                              res

                            On peut également, par exemple, rajouter des fonctions de conversions vers et à partir des tableaux non persistants ainsi qu'une fonction de copie qui oublie tout l'historique des patchs.

                            let to_array = reroot
                            
                            let of_array a = ref (Arr a)
                            
                            let copy pa = ref (Arr (Array.copy (reroot pa))

                            Maintenant, à la manière de la POO, on peut masquer une partie de la mécanique interne ainsi que le type implémentant une telle structure à l'utilisateur. On passe pour cela par le système de modules en n'exposant pas certains détails dans l'interface.

                            (* l'interface du module :
                             *   - la définition du type n'est pas exposée
                             *   - le type 'a data non plus
                             *   - la fonction reroot reste privée *)
                            
                            module type PA = sig
                              type 'a t
                              val init : int -> (int -> 'a) -> 'a t
                              val length : 'a t -> int
                              val get : 'a t -> int -> 'a
                              val set : 'a t -> int -> 'a -> 'a t
                              val iteri : (int -> 'a -> unit) -> 'a t -> unit
                              val iter : ('a -> unit) -> 'a t -> unit
                              val to_array : 'a t -> 'a array
                              val of_array : 'a array -> 'a t
                              val copy : 'a t -> 'at
                            end
                            
                            (* le code complet du module *)
                            
                            module Persistent_Array : PA = struct
                              type 'a t = 'a data ref
                              and 'a data = |Arr of 'a array 
                                            |Diff of int * 'a * 'a t
                            
                              let init n f = ref (Arr (Array.init n f))
                            
                              let rec reroot pa = match !pa with
                                |Arr a -> a
                                |Diff (i, v, pa') -> 
                                   let a = reroot pa' in
                                   let old = a.(i) in
                                   a.(i) <- v;
                                   pa := Arr a;
                                   pa' := Diff (i, old, pa);
                                   a
                            
                              let length pa = Array.length (reroot pa)
                            
                              let to_array = reroot
                            
                              let of_array a = ref (Arr a)
                            
                              let copy pa = ref (Arr (Array.copy (reroot pa))
                            
                              let get pa i = (reroot pa).(i)
                            
                              let iteri f pa = Array.iteri f (reroot pa)
                            
                              let iter f pa = Array.iter f (reroot pa)
                            
                              let set pa i v = 
                                let a = reroot pa in
                                let old = a.(i) in
                                a.(i) <- v;
                                let res = ref (Arr a) in
                                pa := Diff (i, old, res);
                                res
                            end

                            Illustration du module :

                            module PA = Persistent_Array;;
                            
                            let tab = PA.of_array [|0; 1; 2; 3|];;
                            val tab : int PA.t = <abstr> (* la représentation est abstraite pour l'utilisateur *)
                            
                            let tab' = PA.set tab 0 4;;
                            val tab' : int PA.t = <abstr>
                            
                            (* le tableau est bien persistant *)
                            PA.to_array tab;;
                            - : int array = [|0; 1; 2; 3|]
                            
                            PA.to_array tab';;
                            - : int array = [|4; 1; 2; 3|]

                            Il reste à noter, tout de même, que la fonction reroot qui réorganise la structure des pointeurs n'est pas thread safe (les merge peuvent créer des conflits ;-), donc la structure ne l'est pas non plus. On pourrait, par exemple, la rendre thread safe en rajoutant un verrou que l'on pose au début de reroot et qu'on libère une fois la réorganisation effectuée.

                            Voilà, à mon avis, une structure qu'il peut être intéressant d'implémenter avec des shared_ptr plutôt qu'avec de « simples » pointeurs — si j'en ai bien compris le principe. Et encore une fois : la persistance des données, c'est le bien ! :-)

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 3. Dernière modification le 09/10/16 à 22:01.

                            Je viens de réaliser qu'il y a un bug dans le code de mon module sur les tableaux persistants : les codes des fonctions to_array et of_array permettent de manipuler la mémoire partagée en dehors des fonctions fournies par le module, et donc de casser l'invariant. :-/

                            Au passage ça me permet d'illustrer ce qui se passe sur cette mémoire partagée.

                            let shared = [|0; 1; 2; 3|];;
                            
                            let tab = PA.of_array shared;;
                            val tab : int PA.t = <abstr>
                            
                            let tab' = PA.set tab 0 4;;
                            val tab' : int PA.t = <abstr>
                            
                            (* maintenant la zone partagée contient le tableau tab' *)
                            shared;;
                            - : int array = [|4; 1; 2; 3|]
                            
                            (* dès qu'on opère sur tab, c'est lui qui se retrouve dans shared *)
                            PA.get tab 0;;
                            - : int = 0 
                            shared;;
                            - : int array = [|0; 1; 2; 3|]
                            
                            (* par contre si on modifie shared, cela modifie les tableaux sensés être immuables *)
                            Array.set shared 1 6;;
                            - : unit = ()
                            
                            PA.to_array tab;;
                            - : int array = [|0; 6; 2; 3|]
                            
                            PA.to_array tab';;
                            - : int array = [|4; 6; 2; 3|]

                            La solution consiste à effectuer une copie des tableaux mutables pour conserver « l'encapsulation » et maintenir l'invariant.

                            let to_array pa = Array.copy (reroot pa)
                            
                            let of_array a = ref (Arr (Array.copy a))
                            
                            let shared = [|0; 1; 2; 3|];;
                            val shared : int array = [|0; 1; 2; 3|]
                            
                            let tab = PA.of_array shared;;
                            val tab : int PA.t = <abstr>
                            
                            (* si on modifie shared, le tableau tab reste bien inchangé *)
                            Array.set shared 0 5;;
                            - : unit = ()
                            
                            PA.to_array tab;;
                            - : int array = [|0; 1; 2; 3|]

                            Au passage, cet exemple m'aura permis de montrer que l'on peut également faire de la gestion de mémoire bas niveau en OCaml : pointeur en lecture, pointeur en lecture-écriture, pointeur sur pointeur… bien que la libération de la mémoire soit automatisée et déléguée au runtime et au GC.

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 1.

                        Pour conclure, je veux bien un cas d'usage quotidien de shared_ptr. Si je peux être détrompé, tant mieux.

                        Quelques cas. Un premier, c’est de transférer la propriété sur un objet non movable (c’est à dire, en remplacement de std::unique_ptr, pour des objets pas prévus pour / du code legacy). Je t’accorde que je triche, mais il ne faut pas oublier que shared_ptr est antérieur à unique_ptr, et que pour cet usage il était très largement supérieur à auto_ptr.

                        Un deuxième, c’est typiquement quand tu veux passer un observateur optionnel à un objet qui fait un traitement. Dans ce cas, ça peut être le couple weak_ptr / shared_ptr qui peut être utile (si tu souhaites que ce soit l’appelant qui gère la durée de vie de l’objet, mais qu’il puisse supprimer l’observateur en cours de traitement).

                        Sinon, c’est je par exemple aussi beaucoup utilisé dans les frameworks de futures (parce que dans ce type de frameworks, la responsabilité de la durée de vie des callbacks est clairement partagée).

                        Bref, comme beaucoup de choses en C++ : ce n’est pas le truc à tout faire, mais il y a des cas d’utilisation pour lesquels c’est l’outil le plus adapté.

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

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  Le problème c'est que je n'ai jamais vu ces bonnes pratiques décrites de manière un minimum formelle

                  Pourtant, entre les bouquins de Myers et de Sutter/Alexandrescu, y'a de quoi faire niveau bonnes pratiques recommandees et acceptees assez largement.

                  et qu'il faut composer avec l'existant.

                  Ca oui, et tu n'as pas le controle sur l'existant. Le mieux que tu puisses faire, c'est de changer petit a petit le code pour le rendre plus sain a mesure que tu effectues des operations de maintenance dessus. Ca inclut aussi le fait de rajouter -std=c++14 (ou au moins -std=c++11) sur la ligne de compilation. Et donc que ta boite a un processus qui le permette.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 3.

                Pour ton problème de suppression de case (vient de SO):
                vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());

                Bon, je comprends ta defense de C++. En particulier, ses utilisateurs reguliers seront les premiers a rappeler que C++ est concu sur le principe de ne payer que ce qu'on utilise, et je trouve que ca inclut la complexite liee aux features,

                Ceci etant dit, ta "solution" n'est pas valable. D'un cote on demande d'avoir dans la bibliotheque standard, une operation vector::erase_at(size_t idx) et de l'autre tu reponds qu'il "suffit" de faire des circonvolutions. Si c'est une operation recurrente, et consideree comme utile, il me semblerait pas anormal de l'inclure (avec les precautions habituelles : ce genre d'operation sur un vecteur a une complexite O(n) etc).

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 5.

                  on demande d'avoir dans la bibliotheque standard, une operation vector::erase_at(size_t idx)

                  Je vais me répéter mais cette opération ne sert à rien ! Toute la bibliothèque standard fonctionne avec des itérateurs et donc il est logique d'avoir un erase à base d'itérateurs et pas à base d'indice. Autre avantage, ça permet d'avoir une API unifiée pour tous les conteneurs (dont la plupart n'ont pas le concept d'indice, ou alors pas avec un accès aléatoire). Je veux bien que tu me cites un cas pratique de l'utilisation d'une telle fonction avec un exemple suffisamment long pour être pertinent.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 3.

                    J'ai lu tes commentaires bien après avoir écrit ma réponse (je suis arrivé bien après la bataille ;-)). Ton argumentation m'a convaincu (enfin dans sa majorité).

                    Ceci dit, de meme que string est un conteneur "special" avec des méthodes propres (mais qui conserve une grande partie des fonctions des conteneurs "normaux") je ne vois pas pourquoi vector, qui est très clairement une structure de données faite pour être indexée, ne pourrait pas avoir un jeu de fonctions qui, elles aussi, permettraient de manipuler les données en fonction des indices. Apres tout, c'est déjà ce que font operator[] et vector::at, non ?

                    Concernant le cas pratique d'utilisation, tu noteras que je n'ai pas dit que j’étais d'accord, mais bien que

                    Si c'est une operation récurrente, et considérée comme utile, […]

                    J'utilise C++ assez régulièrement, mais dans un contexte particulier qui fait que je n'utilise que des parties très particulières et restreintes du langage (un peu de templates, parfois avec meta-programmation; un peu de POO; et rarement des cas vraiment tordus - meme si ca arrive quand meme).

                    Bref. Non, je n'ai pas besoin de ce genre de feature dans mon cas d'utilisation (si j'avais fréquemment besoin de supprimer un element a l'indice i dans un vecteur dans le contexte de mon boulot, je me demanderais sans doute si je n'avais pas peut-être mal choisi ma structure de données).

                    Dans le cas qui nous occupe, un intervenant dit qu'il a besoin de cette operation (et visiblement pour lui c'est un besoin récurrent). Quelqu'un lui répond, et donne une solution qui est quand meme plus compliquée que ce que veut l'intervenant original (mais qui peut sans doute être écrite une fois pour toutes sous forme de fonction template, certes). Et surtout, jusqu'à ton post, personne n'a demandé la fréquence d'utilisation d'une telle fonction (et c'est une question très pertinente). Ma réponse n'était pas une validation du besoin, mais une façon de montrer encore une fois comment lorsque quelqu'un dit "je veux faire X simplement" on lui répond "T'as qu'a écrire f(Z,Y,W) et ca te donnera X". :-)

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 6.

              ABI des smart pointers n'est pas définie ; donc l'interopérabilité du code n'est garantie que pour des libs headers-only.

              Aucune ABI n'est définie pour C++. Et l'ABI est différente suivant les compilateurs (même si Clang et GCC sont alignés) et même dans l'histoire d'un même compilateur (GCC a changé d'ABI C++ plusieurs fois au cours de son histoire, et je ne parle même pas de MSVC). Et en fait, quand tu as une bibliothèque header-only, tu es sûr que ça va marcher partout sans problème d'ABI justement puisque tout est recompilé à chaque fois. Tandis que ta bibliothèque, il faut qu'elle soit compilé quasiment avec la même version de compilateur (notamment sous Windows) que ton programme si tu veux que ça fonctionne sans problème.

              on pense à introduire une fonction pour enlever un élément d'un vecteur avant le générateur de gaussiennes.

              Un truc comme vector::erase qui existe depuis 98 ?

              Parce que ça implique de maintenir plein de choses dans le langage juste pour conserver cette compatibilité, ce qui freine l'évolution du standard.

              C'est un choix. Que ce choix soit critiquable, OK. Mais ce choix est le résultat d'un consensus : les gens qui font du C++ sérieusement préfèrent avoir une compatibilité. Même s'il la casse quand même quand c'est nécessaire. Personne ne t'oblige à l'utiliser.

              En C++, on a toujours pas de librairies pour le système de fichiers.

              Si justement, on en a une maintenant. C'est la même que celle de Boost. Et croire que gérer un système de fichier, c'est facile, c'est se tromper lourdement. Parce que C++ étant compatible avec C, et C étant à la base des systèmes d'exploitation, il faut être compatible avec toutes les bizarreries. Comme par exemple le fait que sous Unix, les noms de fichiers soit des char * mais sous Windows des wchar_t *. Juste ça, ça fout le merdier pour faire un code portable. Avec un langage neuf, tu peux te permettre de cacher ça et de faire des conversions. Mais en C++ où tu t'attends à de la performance, tu dois en tenir compte et faire avec.

              Et pour ça, C++ ne part pas gagnant.

              Sauf que C++ ne part pas, il est déjà très bien installé et n'est pas près de bouger. Des projets C++ se créent tous les jours.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 3.

                Les bizarreries au niveau du système de fichiers, comme ceci étant lié à la plateforme, tu peux simplement le gérer lors de la compilation. Aucune surcouche lors de l’exécution. Je ne vois vraiment pas où est le problème.

                Dire que des projets C++ se créent tous les jours n’apporte rien, des projets en Brainfuck aussi. Il y a même des gens pour commencer des projets en Javascript.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 1. Dernière modification le 03/10/16 à 11:37.

                Un truc comme vector::erase qui existe depuis 98 ?

                Je suis peut-être exigeant là-dessus, mais entre vec.erase(vec.begin() + 32); et vec.delete_at(32), je trouve le second bien plus lisible. Et je trouve d'autant plus dommage que le second ne soit pas dans la stdlib au vu de sa facilité d'implémentation.

                Personne ne t'oblige à l'utiliser.

                Le thread part d'un gars qui demandait ce que l'on reprochait au C++, ce n'est pas dans ce thread que je vais donner toutes les parties que j'aime bien.

                Si justement, on en a une maintenant.

                De ce que je vois, c'est une “optional technical specification”.

                Sauf que C++ ne part pas, il est déjà très bien installé et n'est pas près de bouger. Des projets C++ se créent tous les jours.

                Tu déformes ma phrase. Je ne parle pas de son existence dans le monde du dev, c'est évidemment un des langages pilier de notre milieu. Je parlais de réduire les frictions de développement.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 1.

                  Je suis peut-être exigeant là-dessus, mais entre vec.erase(vec.begin() + 32); et vec.delete_at(32), je trouve le second bien plus lisible. Et je trouve d'autant plus dommage que le second ne soit pas dans la stdlib au vu de sa facilité d'implémentation.

                  Sauf que c'est complètement inutile parce que, par exemple, tout un tas d'algorithme générique renvoie des itérateurs (ou des couples d'itérateurs pour marquer le début et la fin d'un intervalle) genre std::find et donc, si tu veux effacer ces éléments, tu es bien content d'avoir erase et ton delete_at serait parfaitement inutile.

                  De ce que je vois, c'est une “optional technical specification”.

                  Tu vois mal, c'est marqué en haut dans un cadre avec un gros I devant: «Merged into ISO C++. The functionality described on this page was merged into the mainline ISO C++ standard as of 3/2016; see the filesystem library (since C++17)» avec un lien direct vers la bibliothèque de C++17.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 5. Dernière modification le 04/10/16 à 23:17.

                    tu es bien content d'avoir erase et ton delete_at serait parfaitement inutile.

                    Je n'ai jamais dit que c'est mutuellement exclusif.

                    la bibliothèque de C++17.

                    Oui, donc un standard qui n'est pas encore d'actualité.

                    Maintenant, que tu n'aime pas Rust, c'est ton droit le plus complet. Que tu aimes C++, c'est aussi ton droit le plus complet. Par contre, sortir des attaques ad hominem comme dans tes autres commentaires et pré-supposer que tes interlocuteurs sont des billes en C++, c'est un manque de courtoisie des plus élémentaires.

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 1.

                      Maintenant, que tu n'aime pas Rust, c'est ton droit le plus complet.

                      Ce n'est pas que j'aime ou que je n'aime pas, je m'en fous.

                      Par contre, sortir des attaques ad hominem comme dans tes autres commentaires et pré-supposer que tes interlocuteurs sont des billes en C++, c'est un manque de courtoisie des plus élémentaires.

                      Je ne suppose rien, je lis des choses qui sont inexactes et qui servent de base à des dénigrements contre C++. J'en corrige certaines mais je n'ai malheureusement pas le temps pour faire une passe complète. Et quand je lis autant d'approximations, je me dis que les auteurs ne doivent pas être de fin connaisseurs de C++ et qu'ils feraient mieux de ne rien dire. Est-ce qu'exiger qu'on se renseigne un minimum avant de sortir des âneries, c'est devenu un prérequis trop important, même sur linuxfr ?

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 5.

                        Est-ce qu'exiger qu'on se renseigne un minimum avant de sortir des âneries, c'est devenu un prérequis trop important, même sur linuxfr ?

                        Fais gaffe à toi on est toujours l'idiot d'un autre. La notion même de prérequis pour pouvoir commenter est une ignominie. Que tu trouve dommage que les gens posent des affirmations qui sont inexactes ou plus d'actualité (ou qui ne seront plus d'actualité plus tard (sic)), c'est une chose. Trouver que des gens ne devraient se taire en est une autre.

                        Pourquoi es-tu si sensible ? Que les commentaires de cette dépêche ne te plaise pas, c'est pas grave. Il y a toute une série de dépêche et c'est le langage qui a le plus de journaux. Tu aura pleins d'occasion de discuter de sujets qui te plaisent plus en restant autour de ton langage.

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

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 3.

                          Pourquoi es-tu si sensible ?

                          Parce que je deviens vieux et irascible beaucoup plus facilement.

                          Que les commentaires de cette dépêche ne te plaise pas, c'est pas grave. Il y a toute une série de dépêche et c'est le langage qui a le plus de journaux. Tu aura pleins d'occasion de discuter de sujets qui te plaisent plus en restant autour de ton langage.

                          Parce que tu crois vraiment que le fait de voir débouler sur un journal ou une news qui parle de C++ tout un tas de gens qui viennent t'expliquer que Rust/Go/whatever c'est vachement mieux et que t'as rien compris si tu utilises encore C++, ça incite à faire des journaux ou des news sur C++ ? Et bien non, je me dis que j'ai mieux à faire ailleurs, que ça serait une perte de temps.

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 2.

                            voir débouler

                            Tu les a vu où à part sur cette dépêche ? La précédente dépêche n'avais pas du tout cette teinte, ni les 2 derniers journaux.

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 2.

                        je lis des choses qui sont inexactes

                        Du coup, tu peux les corriger avec des faits? C'est plutôt ça qui serait utile.

                        En particulier, les choses qui étaient vraies et qui ne sont plus vraies, c'est tout à fait possible qu'on puisse ne pas être au courant. Une telle erreur peut être faite de bonne foi, et si tu as l'info permettant de la rectifier, je ne vois pas pourquoi te priver de répondre poliment.

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 1.

                          Du coup, tu peux les corriger avec des faits? C'est plutôt ça qui serait utile.

                          Comme ici par exemple, où je t'ai donné une réponse technique qu'on peut retrouver ou encore . Bref, je n'ai pas donné mon opinion, j'ai juste relaté le pourquoi du comment en relayant l'explication quasi-officielle. Et pourtant, cette réponse est à (+1/-2). Tu crois vraiment que je vais continuer pour me prendre des (+1/-2) et au passage m'entendre dire dans la suite des commentaires qu'en fait, ça devrait être simple. Ben non, j'arrête là et tant pis.

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 2.

                            au passage m'entendre dire dans la suite des commentaires qu'en fait, ça devrait être simple

                            Désolé d'avoir émis une opinion et de ne pas savoir que les modules régleront ça.

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 5.

                            Honnêtement, je ne comprends pas à quelle catégorie d'erreur tu faisais allusion (dans quelle mesure est-ce que la critique de la non-gestion des modules est-elle obsolète?).

                            Tu as une manière "officielle" de gérer les en-têtes, c'est 1) d'inclure les fichiers .h à l'aide d'une directive du pré-compilateur, ce qui reste un procédé hyper-bourrin (en gros, un copié-collé du header, qui sera recompilé pour chaque .cpp), et 2) d'inclure "à la main" des directives du pré-compilateur pour s'assurer que le header ne sera inclus qu'une fois. Autant dire que les modules ne sont absolument pas gérés, il faut tout faire soi-même. Le langage ne sait même pas ce qu'est un module, la seule chose qu'il sait faire c'est d'acceper une déclaration dans le même fichier an amont de la définition. C'est tellement une limite du langage que les compilos ont développé des pragma (à l'origine absolument pas standard) pour faciliter un peu la vie.

                            À mes yeux, ta réponse confirme simplement ce que d'autres essayent de dire dans ce fil : en C++, la gestion des modules est quasi-inexistante, et repose sur un procédé obsolète et bancal. Les pragma once et autres sucres syntaxiques ne résolvent pas le problème, et tu ne fais qu'insister sur le fait qu'il s'agit de solutions techniquement bancales. Du coup, on est tous d'accord, j'ai juste l'impression que tu trouves le système par défaut acceptable (et, de facto, il l'est puisqu'on l'utilise tous). C'est juste un peu dommage que tu ne sembles pas voir l'intérêt d'un système de modules moderne.

              • [^] # Re: Donc pour résumer…

                Posté par (page perso) . Évalué à 2.

                Aucune ABI n'est définie pour C++.

                A bon? Et ça c'est quoi alors:
                https://mentorembedded.github.io/cxx-abi/abi.html

                Si tu parle d'ABI qui fait partie du langage, je ne connais pas un seul langage qui a une ABI définie de la sorte. C non plus n'as pas d'ABI.
                Il fût un temps ou les différent compilateurs C n'étaient eux aussi pas compatibles. (__stdcall, __cdecl, __fastcall, … ?)

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 5.

                  A bon? Et ça c'est quoi alors:

                  Comme son nom l'indique, c'est l'ABI C++ pour l'architecture Itanium. Et pas l'ABI C++ pour n'importe quelle archi. Il y a aussi celle pour ARM et on pourrait continuer la liste.

                  C non plus n'as pas d'ABI.

                  Tout à fait, mais c'est quand même moins pire que pour le C++ de mon point de vue.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 3.

                  Le truc, c'est que le C est un langage tellement simple (pas d'héritage, donc pas de méthodes virtuelles donc pas de vtable, pas de RTTI, pas d'exceptions) que c'est relativement simple d'avoir des ABI compatibles (je ne dis pas qu'elles le sont toujours) entre divers fournisseurs.
                  C'est d'ailleurs pour ça que tout le monde passe par le C pour discuter les uns avec les autres.

                  Pour les autres langages, souvent plus riches en fonctionnalités, je dirais qu'il y a 2 cas:

                  Si on prend un langage «propriétaire», non pas dans le sens code source fermé mais dans le sens qu'il n'existe qu'un compilo et que c'est lui la norme, le problème se résous de lui-même.

                  Mais dans le cas d'un langage comme C++ (doit y en avoir d'autres je pense), qui est un standard ISO pouvant être implémenté par de nombreux acteurs et sur tout type de matériels, le fait de ne pas avoir d'ABI standardisée (dans les spec. du langage) me semble être un pré-requis, pour la faisabilité (diversité des acteurs, code fermé & P.I., etc.) comme pour les possibles optimisations dépendant des architectures. Enfin, pour les optimisations, c'est ce que je me suis laissé dire: n'y connaissant pas grand chose, je ne peux juger.

                  • [^] # Re: Donc pour résumer…

                    Posté par (page perso) . Évalué à 0.

                    Je ne vois pas bien l'intérêt d'une ABI universelle standardisée. ça n'est d'ailleurs pas le cas pour le C.

                    L'ABI comprend par exemple:
                    - La façon dont les noms de fonctions/méthodes sont traduits en noms de symboles (mangling, en C++), et les cas dans lesquels il peut y avoir des conflits,
                    - La façon dont les paramètres d'une fonction sont passés,
                    - Les règles d'alignement des membres d'une structure.

                    Si on peut arriver à utiliser le même mangling partout pour le 1er point, les autres sont très liés à l'architecture matérielle et aux possibilités du CPU utilisé. Les paramètres d'une fonction peuvent être passés dans des registres ou sur la pile. En commençant par celui de droite ou celui de gauche. En faisant éventuellement les choses différemment selon la taille des paramètres, ou même selon leur type (par exemple sur un motorola 68000, il y a des registrer pour stocker des entiers, et d'autres pour stocker des pointeurs).

                    Donc, on ne peut pas demander mieux que d'avoir une ABI par famille de CPUs. ARM en fournit une, avec des variantes en fonction de l'OS utilisé. Sur x86, il n'y en a pas et c'est laissé au choix de l'OS et du compilateur.

                    En fait, les problèmes arrivent surtout quand il y a plusieurs ABI "plus ou moins" compatibles. C'est le cas en C++ sous Windows, par exemple, ou on peut lier des dll compilées avec différents compilateurs, et tout marchera bien, sauf les exceptions.

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 1.

                      Je suis d'accord, ce n'est pas possible et même probablement pas souhaitable d'avoir un truc unifié multi-arch.

                      Ceci étant dit, le mangling, justement, est ce qui force à repasser en mode "struct&pointers" quand on veut faire joujou avec des plug-ins, et l'on n'est même pas sûr que d'un compilateur à l'autre, voire d'une version à l'autre d'un même compilo, il ne faille pas tout recompiler.

                      Après, les exceptions… personnellement, j'évite, et si j'utilise, je ne les fais pas sortir de leur binaire. D'une part, je trouve pas ça si terrible que ça en terme de maintenance, et d'autre part, si une exception n'est jamais rattrapée, c'est un comportement indéfini, et comme elles ne sont pas toujours super documentées… je ne les hais pas, je m'en méfie juste.

                      • [^] # Re: Donc pour résumer…

                        Posté par (page perso) . Évalué à 4.

                        le mangling, c'est juste le premier truc qui se voit et qui empêche que ça marche. Si tes compialteurs ont le même mangling (comme en C, vu que c'est souvent juste "on met le nom de la fonction dans le symbole", avec éventuellement un underscore en préfixe), tu peux avoir aussi des problèmes beaucoup plus chiants à débugger, quelques exemples au hasard:
                        - Les deux compilateurs ne mettent pas les arguments sur la stack dans le même ordre
                        - Les long int ne font pas la même taille
                        - Un des deux compilateurs choisit de passer des paramètres via des registres, l'autre non (ou pas les mêmes paramètres dans les mêmes registres)
                        - La gestion des varargs est différente
                        - …

                        Donc, mélanger les compilateurs, même si on fait du C ou du "struct&pointers", c'est dangereux. Sauf s'il y a une ABI définie pour la plateforme qui précise tout ça (et pas juste le mangling). Et quand il n'y a pas d'ABI standard, finalement, il vaut peut être mieux que le mangling soit différent, au moins, on voit le problème à l'édition de liens (ou lors du chargement du plugin) et pas à l'exécution (on fait pas du Python, ici ;)).

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 2.

                          Donc, mélanger les compilateurs, même si on fait du C ou du "struct&pointers", c'est dangereux.

                          YOLO… triste, mais… comment dire… tellement vrai.

                          À l'heure actuelle, il me semble que ce qu'il se fait c'est du travail sur les conventions C (cdecl & co) et ça permets d'avoir des plug-ins qui marchent à peu près. C'est pour ça que la compat C est si importante: c'est elle qui nous permets de causer, c'est notre lien, c'est la langue indoeuropéene des langages informatique.
                          De là même façon qu'en électronique, il me semble me souvenir que par convention, l'intensité est inversée par rapport aux fait.
                          Il serait difficile d'imposer un standard par arch de toute façon, parce que qu'il existe plusieurs OS avec des historiques variés (windows NT, *BSD, linux, solaris, whatever).
                          Maintenant certains constructeurs le font, c'est une bonne chose pour l'interop. Mon avis, c'est que les gouvernements devraient obliger par la loi l'interop, avec des formats standards en fonction du matos (et être compétents, ainsi que mettre force de loi à des standards entre composants histoire qu'on cesse de jeter à l'excès, je m'écarte du sujet).

        • [^] # Re: Donc pour résumer…

          Posté par . Évalué à 7.

          erreurs catastrophiques

          Existe-t-il un langage qui empêche complètement les erreurs catastrophiques? Qui n'expose jamais au risque de perdre des données, qui génère des programmes dont on est sûrs à 100% qu'ils ne planteront jamais?
          A priori, ADA pourrait coller à la description, mais, je ne l'ai pas vu cité encore. J'ai vu cités Swift, Go, et d'autres dont je n'ai jamais entendu parler. Ceux-la, ils y collent à la description? Et en pratique, ça donne quoi?

          temps de compilation ;

          Faut dire que, quand on voit le code le STL de g++, on comprend que ce soit long à compiler… ça s'include dans tous les sens, avec des macros en veux-tu en voila, des héritages et aggrégations tortueux… et de manière générale un code illisible (chose amusante: ils indentent parfois avec des espaces, parfois avec des tabulations. Je ne parle pas d'aligner le code, mais bien de l'indentation!).
          Je n'ai pas fait de comparaisons sur les temps de compilations, mais depuis quelques temps j'utilise libc++ de llvm. Rien que pour comprendre pourquoi un truc merde, c'est 10 fois plus simple (même si je reste gêné par le fait qu'il soit impossible d'interdire au compilo les inline quand on debug…).
          Au final, j'utilise clang parce que plus rapide et plus clair que gcc depuis plusieurs années, et cette année j'ai commencé à utiliser leur STL.
          Plus ça va, moins j'utilise d'outils GNU pour le dev, et mieux je me porte. Ca ne m'empêche par de reconnaître que je leur dois une fière chandelle, hein, mais je pense que certains défauts repprochés au langage C++ sont plus dus aux outils libres «standards de facto» qu'au langage lui-même. Ou du moins, que ces fameux outils sont une cause aggravante. Le poids de l'historique, sûrement.
          Ah oui, j'oubliai: j'essaie aussi de tendre vers l'utilisation de musl, histoire qu'on me dise pas que j'ai oublié la libc :) (mais bon, oui, il me reste pas mal de trucs à remplacer, faut du temps, surtout que je fais ça pour le fun surtout).

          le langage de templates qui est parmi les plus abscons, parce qu'il mélange les génériques et la métaprogrammation (ou alors je suis le seul à trouver les templates récursifs imbitables) ;

          Existe-t-il un autre langage natif ayant un mécanisme de templates aussi puissant que celui du C++, et qui offre les mêmes perfs (à peu près, du moins)?
          Rust, peut-être? (vraie question)

          le préprocesseur, vraiment ?

          Malheureusement, on en a besoin pour pouvoir utiliser des trucs codés en C. Dans certains cas, ça permets aussi de réduire le code boiler-plate.
          Mais, c'est vrai: ce truc est un générateur de galères, quand il est utilisé abusivement. Les fonction inlinées sont par exemple bien plus intéressantes que les macros (surtout en combinaison avec les template), et pourtant dans certains bouts de code on voit encore des macros.
          La dernière fois que j'ai vu des macros utilisées, c'était soit dans du code C, soit dans des framework de test, cela dit.
          Reste donc que l'usage d'import de header, et je suis d'accord pour dire que ça serait pas mal qu'il saute.

          (pourquoi on ne peut pas définir une classe template ailleurs que dans son header ?)

          Tu peux. Il suffit qu'elle soit déjà définie quand elle est utilisée. Ca m'arrive de faire des petites structures templates internes à un fichier, que je n'expose pas publiquement ça aucun intérêt.
          Je me doute que tu le sais, mais ce n'est pas nécessairement le cas de tous, je me permets donc cette clarification :)

          'évolution rachitique

          Vrai. C'est lent.
          Je me demande quelle est la part de responsabilité liée à l'ISO, ceci dit. La plupart des langages qui sortent, au final, n'ont qu'une implémentation de référence, pas de normalisation réelle.
          Hors, cette standardisation officielle et internationale est une force de C++ pour moi.
          Du coup, je comparerai la lenteur d'évolution de C++ à Debian: c'est lent et vieux, c'est vrai, mais le fait que ça n'aille pas trop vite semble avoir pour conséquence que la maintenance est plus simple pour les utilisateurs (un vieux code C++ compile sur un compilo moderne sans trop de soucis, et une Debian peut être up/down gradée de la même manière). Pas la création d'un système fonctionnel, sa maintenance, j'insiste.

          la gestion mémoire qui accumule plusieurs couches au fil des années (pointeurs nus, références, puis pointeurs intelligents [unique_ptr/shared_ptr/etc.]) ;

          Marrant, c'est justement pour la gestion de la mémoire que j'apprécie le C++. Parce qu'en C++, si je veux utiliser un GC, je peux. Si je veux utiliser des pointeurs intelligents, je peux. Si je veux utiliser des pointeurs nus, je peux encore!
          J'ai le choix, bon sang, et c'est bien d'avoir le choix, de la meme manière que c'est bien de pouvoir choisir entre windows, mac OS, Debian, voidlinux, FreeBSD, OpenBSD, etc.

          C'est perfectible, bien sûr, mais quand je lis qu'il est obligatoire d'utiliser des pointeurs nus en C++, je comprend que l'auteur de la phrase lue ne connaît pas le C++ (je ne dis pas l'avoir lue ici, mais j'ai déjà lu ça, plus d'une fois).
          Sincèrement, avec la STL, les références et les itérateurs, les pointeurs nu ne sont simplement qu'une façon de s'interfacer avec le C ou les vieilles bibliothèques. Rien de plus.
          A noter un repproche que je fais aux pointeurs intelligents standard: ils ne savent gérer que la mémoire, alors que ce n'est qu'une seule des ressources que l'on peut manipuler (alors qu'un descripteur de fichier UNIX est un int, par exemple). Sans parler du fait qu'ils aient un overhead pour moi inutile (évident pour shared_ptr, moins pour unique_ptr, mais le fait est qu'un pointeur pour stocker l'@ du destructeur est maintenu et testé, alors que ça aurait pu être passé en template. Mais j'imagine que certains ont l'usage de modifier le destructeur au runtime?).

          Maintenant, oui, je l'admets: utiliser des références ou des itérateurs ne protège pas contre les segfaults, et il n'y a rien à ma connaissance en C++ pour gérer ça. C'est un des points qui font que je veux me mettre à Rust, un jour.
          Le C++ ne permets pas non plus, à ma connaissance, de vérification sur les under/over-flow d'entier. C'est un problème, pour moi, vraiment. D'un autre côté, ça permets un code plus léger et plus rapide, parce que si à chaque addition on devait faire une vérification à l'exécution, c'est du code en plus à exécuter, de la lenteur.
          Ce que j'apprécierai vraiment, ça serait de pouvoir faire ça, uniquement pendant le debug.

          mais pas de vector::delete_at)

          Et ça n'arrivera jamais, j'espère. Si une telle fonction devait exister, elle aurait plutôt sa place dans les algorithmes, autrement dit, ce serait std::delete_at( iterator it, container& cont ); ou un truc de goût là.
          Les APIs de la STL sont déjà assez moyennes comme ça, à mon avis (à la fois trop riches et pas assez: trop riches parce que, par exemple, std::vector::insert pourrait être implémenté en dehors du conteneur (avec une combinaison de std::move&std::inserter par exemple) alors qu'il est difficile de connaitre le cout mémoire réel d'un conteneur (pour vector c'est faisable, mais pour std::map?) ).
          En tout cas, c'est marrant, dans la liste des défauts de la STL, j'aurai pensé voir std::string au moins une fois (parce qu'elle s'interface relativement mal avec le reste de la lib standard, et que pour une gestion de chaine de caractères, ça ne permets meme pas de diviser facilement… bref je la trouve bancale.).

          rétro-compatibilité avec le C, mais pas vraiment non plus.

          Comme tout les langages sauf le C, en fait. Je pense que l'on devrait arrêter de ce soucier du C, franchement.
          Je pense qu'une bonne part des emmerdes du C++ viennent du C.

          dont une bonne partie sont ÀMHA imputable à son âge.

          Personnellement, si j'apprécie énormément le C++, c'est parce que c'est un langage qui donne le choix.
          Il me donne le choix d'utiliser les template.
          Il me donne le choix d'utiliser le modèle mémoire que je veux (GC, pointeurs intelligents, pointeurs nu. Bon, ok, pour le GC il n'y a rien dans la lib standard, probablement parce que la culture C++ préfère la RAII).
          Il me donne le choix d'utiliser la RTTI (j'inclue dedans les méthodes virtuelles).
          Il me donne le choix de faire de l'héritage multiple et même en diamant (ça m'est déjà arrivé une fois de m'en servir).
          Il me donne le choix d'utiliser les exceptions (à condition de se passer de la STL, certes).

          Rien n'est obligatoire, il faut choisir ce que l'on utilise. Pour certains, c'est un défaut. Pour moi, c'est une force.
          Pour refaire le parallèle avec les distributions Linux: pour moi, l'apparition de Devuan, d'Ubuntu, d'Archlinux, et j'en oublie, ce sont de bonnes choses, parce que ça donne le choix.
          Alors bien sûr, avoir le choix implique de prendre des décisions, mais pour moi c'est un peu le coeur de métier d'un développeur, de prendre des décisions.

          Mais ce n'est pas parce que j'aime beaucoup C++ que je dis qu'il n'a aucun défaut. Seulement, à l'heure actuelle, je ne vois que Rust pour le remplacer, et il est encore jeune, on n'a pas encore eu le temps de voir comment il vieillit.
          Avant de dire que Rust peut remplacer C++, il faudra au moins 5 ans d'écart entre la sortie de sa 1ère version stable et l'heure du jugement, justement pour avoir du recul.
          On n'a aucun recul sur Rust, contrairement à C++ ou l'on parle de près de 20 ans ( ~18 de standard ISO).

          • [^] # Re: Donc pour résumer…

            Posté par (page perso) . Évalué à 2.

            A noter un repproche que je fais aux pointeurs intelligents standard: ils ne savent gérer que la mémoire, alors que ce n'est qu'une seule des ressources que l'on peut manipuler (alors qu'un descripteur de fichier UNIX est un int, par exemple)

            Justement, le deleter sert à choisir comment on restitue la ressource, et de fait on peut employer les pointeurs intelligent du C++11 (et boostien) sur n'importe quel type de ressources.

            auto p = shared_ptr<FILE>(fopen(foo), [](FILE *f){ if (f) fclose(f); });

            Cela manque d'un chouilla de surcouche pour une utilisation aisée. Reste que c'est un cas de figure prévu.

            Le C++ ne permets pas non plus, à ma connaissance, de vérification sur les under/over-flow d'entier.

            On déporte ça à de l'analyse statique, ou aux sanitizers.

            Il me donne le choix d'utiliser les exceptions (à condition de se passer de la STL, certes).

            Pratiquement tous les algos sont neutres relativement aux exceptions.

      • [^] # Re: Donc pour résumer…

        Posté par . Évalué à 9.

        Lui, je sais pas mais moi:

        • une complexité terrifiante, avec un empilement de features parfois obscures, au point que la plupart des projets sérieux définissent explicitement ce qui est autorisé,
        • une syntaxe lourde, illisible et qui fait mal à la tête
        • des undefined behavior en veux tu en voilà
        • gestion manuelle de la mémoire (ca s'améliore)
        • des temps de compilations affreusement long
        • des messages d'erreurs de compilateurs ahurissant
        • une abi plus capricieuse que ma fille de 9 mois
        • des reliques du passé qui ne devraient plus exister dans un langage moderne, genre les header ou l'arithmétique de pointeurs
        • un manque de features moderne (optional, switch/case utile, enums objets, catégories/mixins etc)

        Ca s'améliore ces derniers temps, mais tres, tres lentement.
        Y'a un certain nombre de languages qui avancent sur le créneau "rapide, natif et toujours haut niveau" genre Swift, go ou rust.
        Pour choisir c++ sur un nouveau projet, faut avoir de besoins très spécifiques. A la vitesse à laquelle la concurrence avance, mon petit doigt me dit que sous qq années, la principale raison de faire du c++, ca sera des raisons historiques.

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

        • [^] # Re: Donc pour résumer…

          Posté par (page perso) . Évalué à 0.

          la principale raison de faire du c++, ca sera des raisons historiques.

          Ou pour le futur, car il sera toujours la.
          Car par "genre Swift, go ou rust", tu dis justement qu'il n'y a pas de remplaçant viable à long terme, la mode change toutes les X années (et encore, tu as oublié Python et j'en passe comme "prétendant"), quand il n'y a pas 3 mode en même temps pour le remplaçant.

          Les modes passent, C et C++ sont toujours la et on peut réutiliser du code d'il y a longtemps tout en sachant qu'on pourra réutiliser le code plus tard.

          • [^] # Re: Donc pour résumer…

            Posté par . Évalué à 2.

            Car par "genre Swift, go ou rust", tu dis justement qu'il n'y a pas de remplaçant viable à long terme

            Il y a deux choses. D'abord, l'équivalence entre les objectifs des langages. Swift et Go n'ont pas les mêmes objectifs que C++, ne serait-ce que pour les modèles mémoires (GC + ARC).

            Ensuite, la pérennité. Pour Rust, c'est à voir. Pour Swift et Go, par contre, avec Apple et Google derrière, je pense qu'on peut supposer que ce ne sera pas trop un souci.

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 3.

              Intéresant. Donc, si je te suis bien, le seul langage dans la même catégorie que C++ est Rust, dont la pérénité n'est pas assurée.

              Du coup, l'intérêt de C++ me semble tout trouvé?

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 0.

                Donc, si je te suis bien, le seul langage dans la même catégorie que C++ est Rust

                Dans mon cas d'utilisation. Il y a sûrement d'autres langages que je ne connais pas, ce n'est pas spécialement mon domaine de prédilection, je suis un simple dilettante sur le sujet.

                Par contre, même si la pérennité n'est pas assurée, il y a deux facteurs à prendre en compte. Premièrement, Mozilla n'est pas vraiment une petite boîte, des gens sont payés pour bosser dessus, les versions se succèdent, la doc est de bonne qualité, et la communauté excellente, donc c'est plutôt bien parti. Ensuite, tous les programmes ne sont pas destinés à durer 20 ans, et si tout le monde ne prenait en compte que les langages pérennes, nous serions tous probablement encore en train de programmer en Fortran ou en Cobol.

          • [^] # Re: Donc pour résumer…

            Posté par . Évalué à 3.

            Swift est le langage de prédilection de la plateforme de development la plus populaire du moment, tourne sur quelque centaines de million de devices, et est poussé par la corporation la plus riche de la planète, connue pour faire des choix technologiques a tres long terme. Et qui au passage est aussi connue pour être une brute sur tout ce qui est dev tools, et emploie une des équipe de compilation les plus brillantes de la planète.
            Je pense pas trop m'avancer en pariant que le language sera toujours la dans 10 ans.

            Je voit pas ce que Python vient faire dans la catégorie "rapide, natif et toujours haut niveau", mais je te ferais remarquer que le langage a 25 ans. Dit autrement, il est sorti avant que t'envisages une carrière dans l'informatique. Et probablement même avant que tu comprennes vraiment le sens du mot "carrière".

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

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 2.

              Je voit pas ce que Python vient faire dans la catégorie

              Moi non plus, mais je pense que c'était une provocation pour mettre en exergue le fait que de tous les langages que zenitram à vus cités ici, seul Rust est dans la meme catégorie que le C++ (et le C) notamment pour le fait de ne pas forcer (je ne suis pas sur, mais il me semble que Swift et Go le font) à utiliser un GC.
              Je peux me tromper.

              Ensuite, je suis surpris. Je n'imaginais pas python si vieux. Mais du coup, j'ai une question: quelle garantie a-t-on de pouvoir exécuter un code python de la première heure avec la dernière version stable?
              Je me pose la meme question pour Swift et Go.
              En C++ standard (ansi, ou 98, donc bien moins vieux que la première version de python, on est d'accord), je ne me pose pas de question, je suis sur à 95% que ça se fait sans souci.
              Bon, sans souci… ça ne va peut-etre pas durer longtemps puisque std::auto_ptr sera retiré dans C++17. Par contre, il s'agit de la lib standard, et moi, je distingue la lib standard du langage (chose sur laquelle je suis persuadé que mon avis diverge de la moyenne).

              PS: désolé pour le manque d'accents, petit problème de config clavier Xorg sous void…

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 2.

                Swift n'a pas de gc. Arc est pure compile time, et 100% déterministe.

                Sinon, pour la pérennité, Ben non, t'es à peu près sûr que ca marchera pas.
                Comme pour du c++ de 1995 dans un projet c++17.
                Comme pour du Swift 2.3 dans 10 ans.
                Ou du ruby. Ou plus ou moins tous les langages de la planète, sorti de c et Java, et les brontosaures qui sont figés dans le temps, fortran, cobol etc.

                Et encore, même Java, ton code tu vas vouloir le moderniser.
                Bref, la question c'est pas tant est ce que ca va tourner à l'identique (on a des release binaires et des abi stables pour ca), mais quel est le coût de maintenance au fil de l'eau, et est ce qu'on aura toujours une communauté et un compilo/interpreteur moderne 10 ans plus tard.

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

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 3.

                  Allez, histoire de taquiner un peu : Perl 5 a une rétro compatibilité sur plus de 20 ans. Donc ça se fait ! :D Mais il est clair que c’est l’exception plutôt que la règle.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  Swift n'a pas de gc. Arc est pure compile time, et 100% déterministe.

                  Ok. Faudra que j'y jette un oeil.

                  Comme pour du c++ de 1995 dans un projet c++17.

                  Dis-moi, tu as remarqué que j'ai parlé de C++ standard, et donc de 98?
                  As-tu aussi remarqué que je parlais de prendre un code source du vieux standard et de le compiler avec un compilateur moderne?

                  Si tu réponds oui, alors donne moi un bout de code répondant au 1er standard C++, c'est à dire de 1998 (donc, non, pas de 1995…) que je ne puisse pas compiler avec, par exemple, clang3.8.
                  Quant au C++17, vu qu'il n'est pas encore sorti, ça va être difficile de tester.

                  Et encore, même Java, ton code tu vas vouloir le moderniser.

                  Tout à fait. Mais pour moderniser, il faut encore être capable de compiler l'original.
                  Imaginons un peu, tu tombes un jour sur un logiciel open source sympa mais plus maintenu depuis 10-15 ans (allez, genre, autorealm. J'aimerai vraiment arriver à le recompiler lui… mais c'est du delphi, et je me sens pas motivé pour acheter une licence windows + whatever compilo juste pour moderniser et porter ce logiciel sous linux).
                  Le fait de pouvoir le compiler malgré l'absence de mise à la modernité pendant plus d'une décenie est un peu vital pour pouvoir le moderniser ensuite sans reprendre tout le code source.
                  C'est une des choses que je pense que C++ permets.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 1.

                    Si tu réponds oui, alors donne moi un bout de code répondant au 1er standard C++, c'est à dire de 1998 (donc, non, pas de 1995…) que je ne puisse pas compiler avec, par exemple, clang3.8.
                    Quant au C++17, vu qu'il n'est pas encore sorti, ça va être difficile de tester.

                    Tu vas pouvoir le compiler.
                    Et ton template, ou autre chose de pas kasher dans un header qq part, il va te jeter un warning, ou autre joyeuseté du genre. Et tu vas devoir porter le code, la, maintenant, parce qu'un warning, c'est une erreur, et que "if not now, then when?".

                    Je dis pas ca genre c'est horrible oh mon dieu le c++, Java te ferais la même chose avec des collections non typées, Swift va te casser les couilles sur un des millions de petits trucs qui ont changé depuis, etc.
                    Mon point c'est précisément que du coup vieux de 10 ans qui a pas été mit à jour, c'est du code pas maintenu, et le code pas maintenu, ben c'est plutôt pas terrible, et va falloir s'atteler à la tâche. Ca répondait à zenitram qui sous entendait que tu peux prendre du code vieux de 15 ans, le plopper dans ton projet et roule ma poule.

                    Et oui, clairement, avoir un compilo ca aide pour porter du code. C'est ce que je voulais dire par "est ce qu'on aura toujours une communauté et un compilo/interpreteur moderne 10 ans plus tard.".

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

                  • [^] # Re: Donc pour résumer…

                    Posté par (page perso) . Évalué à 3. Dernière modification le 03/10/16 à 10:22.

                    donne moi un bout de code répondant au 1er standard C++, c'est à dire de 1998 […] que je ne puisse pas compiler avec, par exemple, clang3.8.

                    export template<typename T> class Foo {};

                    J'admets que je triche.

                    Un autre exemple:

                    class B {
                    protected:
                        static int j;
                    };
                    class D : public B
                    { friend void fr(); };
                    void fr() { B::j = 5; }
                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 0.

                      Je suis dubitatif sur ton deuxième exemple. g++ le compile sans broncher, mais je pense qu’il a tort. Je n’ai pas de visual sous la main pour vérifier, mais je pense que c’est clang qui a raison de le refuser (et ce indépendamment de la version de c++, je ne vois pas ce qui aurait changé dans la norme qui provoquerait un changement de comportement).

                      Si tu pars sur des extensions / tolérances du compilateur, tu ne peux pas reprocher au langage lui-même qu’un autre compilateur n’ait pas les mêmes tolérances ;).

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 0.

                        Je suis assez mitigé sur la question. Si le langage est suffisamment clair et simple, il n’y a pas de raison pour que l’implémentation soit compliquée, et que, dans une certaine mesure, le comportement de deux implémentations diverge. Je ne parle pas de cet exemple en particulier, mais plutôt de manière générale.

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 1.

                          Ici, ça me semble clair: c'est B qui définit qui à le droit d'utiliser ses propriétés protégées. Le fait que D considère fr() comme une amie ne donne absolument pas le droit à fr() d'utiliser une propriété protégée de B, uniquement celles de D.
                          Donc pour moi, g++ à tort et clang++ raison. Et je doute que le standard ait changé sur ce point: il faudrait trouver un vieux compilo standard de l'époque pour etre sur.

                          Ceci dit, personne n'a dis que le C++ n'était pas trop compliqué. C'est de notoriété commune (et en plus maintenant on multiplie les façons de gérer un prototype de fonction avec le auto foo() ->type).
                          Une fois ajouté la dessus le fait que certains compilos vont ajouter silencieusement leurs tolérances et extensions si tu ne précises pas un standard, et tu multiplies les risques de confusion, sur un truc déjà complexe.

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 2.

                    Imaginons un peu, tu tombes un jour sur un logiciel open source sympa mais plus maintenu depuis 10-15 ans (allez, genre, autorealm. J'aimerai vraiment arriver à le recompiler lui… mais c'est du delphi, et je me sens pas motivé pour acheter une licence windows + whatever compilo juste pour moderniser et porter ce logiciel sous linux).

                    Tu as (peut-être) été entendu (le dernier commit date de mai 2016) : « Ce logiciel a été écrit pour Windows, en Delphi et n'a plus été maintenu pendant des années ; décision a donc été prise de le réécrire dans des technologies plus modernes comme le C++, openGL et wxWidgets. »

                • [^] # Re: Donc pour résumer…

                  Posté par (page perso) . Évalué à 8.

                  les brontosaures qui sont figés dans le temps, fortran, cobol etc.

                  Pour fortran, il faut être de bien mauvaise foi pour affirmer qu'il soit resté au congélo depuis sa version vedette f77 !

                  Le compilateur GCC (avec son frontal gfortran) propose les versions f95, f2003 et f2008. Les comités de normalisation travaillent assidûment pour la prochaine mouture, f2015, qui devrait sortir bientôt. Fortran est depuis longtemps un langage orienté objet qui peut s'interfacer (quoique pas encore totalement…) avec C/C++ .

                  Pour en avoir un avant goût, un tutoriel (qui date un peu) ou bien les normes.

                  Ce que je ne sais pas, par ignorance totale du C++, c'est lequel des deux langages est le plus avancé.

                  S'il se trouve ici quelqu'un ayant la double compétence en Fortran actuel et en C++, je serais intéressé par son témoignage sur deux points : lequel de ces deux langages est le plus avancé en POO et lequel est le plus rapide une fois compilé ?

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 5.

                    Techniquement avec la fonctionalite de Co-Array, Fortran 2015 (et meme 2008 je crois) est mieux arme pour faire de la programmation parallele (pas trop concurrente) avec un haut niveau d'abstraction. Ceci etant dit, meme si tu as raison de rappeler que F77 c'est vieux, c'est aussi (avec du F90) ce que j'ai vu le plus souvent utilise dans des labos ET dans des boites qui produisent des outils de simulation numerique (avec des solveurs iteratifs).

            • [^] # Re: Donc pour résumer…

              Posté par (page perso) . Évalué à 0. Dernière modification le 03/10/16 à 09:13.

              Swift est le langage de prédilection de la plateforme de development la plus populaire du moment,

              zut, moi qui croyait que c'était Objective-C qui était le futur à apprendre pour les technos Apple, c'est ce que j'avais compris avant que Swift n'apparaisse.
              Bon, je m'y perd ;-).
              (et j’attends le prochain nom dans quelques années)

              connue pour faire des choix technologiques a tres long terme.

              Petite pensée pour Firewire (en premier truc qui me passe par la tête).

              Sinon, cette même entreprise sponsorise un compilateur C/C++ performant, donc si on se base sur la "marque", tu dois avouer que C/C++ est pas mal (ta "marque référence" y met du fric).

              Et probablement même avant que tu comprennes vraiment le sens du mot "carrière".

              Attaque ad hominem, ça en dit long sur le niveau (sérieusement, tu as besoin d'attaquer les personnes qui ne te plaisent pas, de faire ce genre de gaminerie de cours d’école, plutôt que d'attaquer les idées, pour te sentir bien?)

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 3.

                De ce qu’on peut en lire sur Wikipédia, la première version d’Objective-C est sortie il y a 32 ans. Après tant d’années à utiliser un langage, on peut trouver des problèmes et souhaiter les corriger, même en passant par un autre langage. J’espère bien que si on trouve des choses à redire sur un langage au point de vouloir en changer suffisamment la syntaxe pour justifier un nouveau langage, on puisse le faire après trois décennies. Rester avec des technos qui sont moins simples, moins efficaces et moins pratiques sous prétexte de l’historique, très peu pour moi.

                Je te rejoins sur l’idée que « grosse entreprise ≠ pérennité ». Il y a régulièrement des contres-exemples. En ce qui concerne spécifiquement la programmation dans ces entreprises je n’ai pas assez de recul pour apporter un avis intéressant.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 8.

                zut, moi qui croyait que c'était Objective-C qui était le futur à apprendre pour les technos Apple, c'est ce que j'avais compris avant que Swift n'apparaisse.
                Bon, je m'y perd ;-).
                (et j’attends le prochain nom dans quelques années)

                Bon, si tu veux troller avec moi sur les technos apple, va falloir affuter et te documenter un peu mieux que ca.
                Deja, ca fait bien qq années qu'il ya des discussions pour moderniser Objective-C. Apple a mit un bon coup de collier sur la deuxième moitié des années 2000, et clang a fait des merveilles sur la période 2010-2013.
                Ca ne change pas que quasiment tout le monde dans la communauté trouve qu'objective c est vieillot. Pointeurs, header, type scalaires pas objets (avec acrobaties via NSNumber ou NSValue), enums pourries, collections qui refusent nil, grosso modo toute la partie héritée directement de C a toujours dérangé la plupart des gens.
                Yavait des débats sur le message sending par dessus tout ca, mais c'etait deja moins sujet a troll.

                Bref, ca fait longtemps qu'objc est plus le future d'iOS/OSX. Au mieux, il était le present.
                M'enfin, c'est sur que c'est pas en trollant a l'emporte piece sur linuxfr que tu vas apprendre ca.

                Comme je sens une point d'ironie, voila la liste des technologies officiellement poussées par apple:

                • basic ou assembleur sur l'apple II (1977-1990)
                • pascal, basic ou C sur macos classic (1984-2000)
                • Transition a macosx: Carbon est offert pour une transition aisée, tout en étant clair que c'est du transitionnel
                • En parallele, passage a objective C + Cocoa (2001-present)
                • 2014: Apple annonce Swift en beta. 1.0 est clairement annonce comme experimental (a vos risques et périls). 2015: Apple annonce 2.0. Les choses se stabilisent et le language est production ready (bien meilleur support dans cocoa, language par défaut a WWDC, language par défaut dans les templates Xcode etc.) 2016, Apple open source le runtime

                Voila pour la leçon d'histoire.

                Je vois 4 phases la dedans. Le 3 premieres ont durées au moins 13 ans.
                Je te laisse deviner combien de temps va durer la 4ieme.
                Si tu veux des indices: les seules features qu'objective c a reçu depuis 2014 servent a faciliter l'intefacage avec swift. Tous les ingénieurs d'apple avec qui j'ai bosse sur le campus poussent activement swift auprès des third party. WWDR aussi. Ca va devient dur de trouver un boulot dans le domaine si tu connais pas swift (perso je rejette d'emblée tout resume qui ne mentionne pas swift).
                Voila, en deux ans, objective c est passe de seul language possible a legacy. Tu peux troller ce que tu veux, mais après 15 ans a apple, et une bonne dizaine a next, je trouve pas ca degueu comme duree de vie.

                Apres, on peut aller en detail dans les raisons du changement.
                Principalement le fait que objective c est super pas fiable de base (philosophie c "tu tiens la tronçonneuse toi meme"), et le fait qu'aussi douee soit l'équipe du runtime, ils ont atteint un mur de performance et de design. Ils peuvent tout simplement pas faire évoluer le language sans une cassure de compat'.

                Petite pensée pour Firewire (en premier truc qui me passe par la tête).

                Tu veux dire le truc qu'ils ont supporte de 99 a 2011?
                Effectivement, un bon exemple. Tu noteras aussi que le marche firewire était bien mort depuis la moitié des années 2000. Apple a continue a le pousser malgré le fait qu'usb 2 ait gagne la guerre, et que firewire n'était utilise en gros que pour les camera DV (qui généralement proposait aussi une sortie usb).

                Sinon, cette même entreprise sponsorise un compilateur C/C++ performant, donc si on se base sur la "marque", tu dois avouer que C/C++ est pas mal (ta "marque référence" y met du fric).

                sigh.
                C'est quoi ton point? Qu'apple supporte c++? Support, youpi, apple supporte c++. Et donc? A pas troller a l'emporte piece?
                J'ai dit que c++ allait disparaitre du jour au lendemain? Non, j'ai dit que démarrer un nouveau project en c++ ne ferait probablement pas beaucoup de sens dans qq années, sortis de besoins tres spécifiques.

                Attaque ad hominem, ça en dit long sur le niveau (sérieusement, tu as besoin d'attaquer les personnes qui ne te plaisent pas, de faire ce genre de gaminerie de cours d’école, plutôt que d'attaquer les idées, pour te sentir bien?)

                Ben putain, il te faut pas grand chose.
                En 95, t'etais au college, potentiellement au lycée. A cet age la, on ne sait pas ce que le mot "carrière" veut dire.
                Ce language que tu traites de mode a été dans l'industrie plus longtemps que toi. J'appelle pas ca une mode, perso.

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

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 5.

                  A propos du support de C++ par Apple : il faut le voir differemment. Apple supporte le developpement de LLVM, qui n'est pas un compilateur C++. Il se trouve que Clang a aussi obtenu des financements de la part d'Apple entre autres parce qu'ils avaient besoin de compiler de l'OpenGL, et Objective C etant aussi compile nativement vers C, etc.

                  Bref, le vrai projet soutenu par Apple, c'est LLVM. Clang/Clang++ sont quasiment finances par effet de bord (de ce que j'ai compris--n'hesitez pas a me corriger si je me trompe).

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 2.

                    Bref, le vrai projet soutenu par Apple, c'est LLVM. Clang/Clang++ sont quasiment finances par effet de bord (de ce que j'ai compris--n'hesitez pas a me corriger si je me trompe).

                    Je ne suis pas sûr où tu veux en venir?

                    LLVM a été démarré par Chris Lattner pendant qu'il était prof d'université. Ils utilisaient les frontend de GCC (dragonegg) et LLVM en backend. Puis Apple l'a débauché pour travailler sur les outils xcode. À partir de la ils se sont rendus compte qu'ils voulaient le même frontend pour xcode et le compilateur, d'où l'arrivée de clang.

                    Lattner (et ses équipes) a ensuite travaillé sur Swift.

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 8.

                      LLVM est un projet de recherche pour fournir une infrastructure de compilation modulaire (ils ont eu des sous de la national science foundation, l’équivalent US de l'agence nationale pour la recherche) pour favoriser la recherche et l’expérimentation en compilation. Clang a effectivement été créé ca cause des problèmes de licence et de l'architecture du front-end de GCC (qui lie (liait?) artificiellement le front-end et le back-end pour des raisons plus politiques que techniques). La complexité artificielle liée au maintien de clang-gcc comme front-end a (de ce que j'ai compris) plus contribué a la création de Clang que le front-end de Xcode (apres tout, Xcode n'avait pas forcément beaucoup de mal a utiliser le front-end de GCC avant ça : ils s'en servaient depuis des années…).

                      Maintenant, Apple a adopté LLVM a cause des problèmes de licence liées a la GPL 3 pour GCC (c'est pour ça que pendant la transition, les OS d'Apple étaient coincés avec GCC 4.2 ou 4.3) d'une part, et le fait que le comité de GCC refusait d'intégrer un moyen d'ajouter des passes d'optimisation depuis l'extérieur (ils ont depuis changé d'avis, entre autres grâce au succès et l'adoption de LLVM/Clang par de plus en plus de monde). L'intégration avec Xcode est une des raisons techniques, ainsi que l'avancement limité pour ObjC dans GCC, mais je ne suis franchement pas convaincu que ça a été la raison principale (la politique/le légal a tendance a avoir un poids plus important dans ces cas-la).

                      Pour en revenir a mon point initial : Zenitram et Groumly se disputaient a propos du "langage phare" d'Apple (qui change environ tous les 10-15 ans). Entre autres Zenitram ironisait sur le fait que malgré son langage phare, Objective C, et maintenant Swift, Apple continuait le support d'un compilateur pour C et C++. Je ne faisais que rappeler que Apple a en priorité supporté LLVM, qui contient un middle-end et un back-end. Ils ont ensuite rajouté Clang quand d'autres soucis techniques et légaux se sont rajoutés. Sauf que, a la base, Apple a utilisé LLVM pour optimiser ses codes OpenGL (écrits en C), avant de passer au codage du futur iOS, etc. D'autre part, OS X est toujours écrit en C (au moins la partie Darwin), ce qui nécessite un compilateur C. Enfin, Objective C est complètement compatible avec les binaires C sur le même système (par conception). Il se trouve que, tout comme GCC il y a 10-15 ans, Clang est en train de diversifier le nombre de langages qu'il gère--quelque part c'est logique : maintenant que l'infrastructure est la, autant l'utiliser.

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 5.

                        Sauf que, a la base, Apple a utilisé LLVM pour optimiser ses codes OpenGL

                        Ca a été la premiere application pratique, mais je doute que ca soit pour cette raison qu'ils aient débauche Lattner et mit le paquet sur llvm.
                        Ils l'ont fait, je pense, parce que:
                        - gcc déconnait a plein tube avec leur délire d'architecture monolithique pour empêcher le proprio
                        - leur habitude d'être maitre de leur technologie (du silicon au soft) leur impose d'avoir un control fort sur les dev tools
                        - le passage a la GPL v3 a ete le coup de grace. Plutôt que de forker et se couper du reste de la communauté, ils ont préféré construire une communauté autour de clang/llvm (et ont fait un plutôt bon boulot de ce cote la, la team dev tools est incroyablement ouverte pour une équipe basée a Cupertino).

                        L'optimisation a la volee des shaders OpenGL était juste la premiere application pratique, suffisamment simple a deployer a l'époque.

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

            • [^] # Re: Donc pour résumer…

              Posté par (page perso) . Évalué à 5.

              Par contre Swift me paraît quand même assez limité actuellement si tu veux du multiplateforme (ou simplement que ça tourne sur ta plateforme). Je trouve le langage intéressant, mais j'avais essayé vite fait de le choper à l'époque de l'annonce de la libération de la version 2.0, sauf que y'a juste des binaires pour Ubuntu (qui marchaient pas sous ma debian, j'avais testé quand même). Je précise que j'avais pas testé plus que ça, et je sais que j'aurais moyen d'installer Swift pour jouer avec. Par contre avec Rust, Go, et je parle même pas de C++, je sais que si je publie un projet je peux dire "allez choper le compilater ici (ou utilisez la version de votre distrib) et installez mon projet". Avec Swift actuellement c'est pas le cas, et j'ai pas l'impression qu'Apple soit très motivé pour en faire une plateforme intéressante ailleurs que sur leur environnement.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 2.

                Au début, ils ont fait le minimum pour eux effectivement. Depuis le passage en open source, je suis sûr que la communauté a porté Swift sur d'autres plateformes. C'était un des buts d'ailleurs: améliorer la diffusion de ce langage.

        • [^] # Re: Donc pour résumer…

          Posté par . Évalué à 4.

          une complexité terrifiante, avec un empilement de features parfois obscures, au point que la plupart des projets sérieux définissent explicitement ce qui est autorisé,

          Je pense que c'est en effet le gros, gros point noir du C++. Le reste, c'est une question de choix et de compromis : la gestion des pointeurs nus, on peut ne pas aimer, mais c'est une raison qui fait parfois préférer C++ à d'autres langages.

          Par contre, la complexité, ça exclut. Ce que font la plupart des projets, c'est de se limiter à un sous-ensemble du C++ (genre, pas de templates, pas d'exceptions, etc). Mais dans ce cas, on peut bien se demande pourquoi le langage évolue. On peut aussi avoir peur de ne pas réussir à recruter des gens réellement compétents en C++ (ça vaut aussi pour les projets libres).

          Au rythme où les choses avancent, il va peut-être devenir impossible à un être humain de maitriser le C++ dans son entier, de connaitre les comportements indéfinis, ou de savoir à l'avance si un truc à base de templates va compiler.

      • [^] # Re: Donc pour résumer…

        Posté par (page perso) . Évalué à 10.

        Aller, j'en ajoute (C++ est mon outil de travail 8h par jour depuis plus de 10 ans) en me focalisant sur les problèmes qui m’embêtent vraiment au quotidien et pour lesquels il n'y a pas de solution simple. J'essaye de rester objectif ;)

        • Les casts implicites. Plus particulièrement le fait que les flottants, les ints, les bools, les enums, et j'en passe, c'est la même chose. Le downcast implicite d'une classe vers une autre. Je sais que c'est la philosophie du langage. Combien de fois je me suis fais manger par un int négatif convertit en unsigned…
        • L'absence de programmation générique. J'aimerais c'est pouvoir automatiquement dériver des comportements pour mes classes. À la louche, l'itération, l'affichage, la sérialisation, la comparaison, les relations d'ordre, le hash, l'enumeration, … Je recode ces trucs là pour presque chaque classe et c'est du temps perdu, du code en plus, des tests en plus, des bugs en plus, de la maintenance en plus.
        • L'absence de types algébriques (i.e. des union typés). Je dirais à la louche que 99% des choses que je traite avec de l'héritage (ou des gros hacks degeux) sont des problématique de types algébriques. L'héritage virtuel sera ici trop verbeux et impose des allocations sur le tas (on peut s'en sortir autrement, mais c'est tordu). La solution à base d'union et de flag est un piège dans lequel on ne tombe qu'un fois tellement c'est l'horreur à maintenir après.
        • Pas de pattern matching.
        • Pas de concepts ;)
        • Un système de type limité et hyper verbeux ce qui fait que on essaye de ne pas créer de nouveau type parce que c'est complexe. Je le retrouve tous les jours au travail ou en formation, un développeur C++ est heureux quand il a une classe suffisamment générique pour traiter tous ses cas, alors que moi je serais heureux quand j'aurais 100 classes suffisamment spécifiques pour traiter tous mes cas avec une bonne sécurité. Mais je ne le fais pas parce que maintenir 100 classes en C++ versus une, c'est l'horreur. Faire de la délégation en C++ c'est lourd.
        • Les valeurs par défaut. Beaucoup de classes de la STL imposent que votre type soit default constructible, comme par exemple le deleter de std::unique_ptr. Cela force à mettre des constructeur par défaut complètement abscons à beaucoup d'endroits.
        • Une absence totale de système de gestion de descendances, librairies, packaging. C'est l'horreur de taper à la main la compilation / installation de 25 librairies tierces.
        • Tout est mutable par défaut ;( Là c'est clairement un point subjectif de ma part, mais je n'aime pas.
        • La communauté. N'allez surtout pas sur IRC si vous n’êtes pas un dieu du C++, vous vous ferez allumer et traiter d’incompétent. Expérience personnelle que j'ai mal vécue : je suis aller sur #C++ sur irc.freenode.net pour discuter une subtilité du langage qui m'avait étonnée et on m'a envoyer paître en me traitant d’incompétent qui ne comprenait pas la philosophie du langage. J'ai fais l'erreur dans mon texte de dire que j'avais rencontré cette subtilité pendant le cours de compilation que je donne à l'université et ils m'ont descendu en expliquant que soit j’étais un troll, soit j'étais un mauvais enseignant, mais qu'il n'était pas possible d'être aussi mauvais et que je ferais mieux de me pendre ou de lire un livre de C++ pour débutant.

        Alors pourquoi C++ est-il mon outil de travail si ce langage est si mauvais ? Parce qu'en premier lieu, tous les langages du monde sont mauvais, en second lieu, c'est le seul à l'heure actuel qui m'apporte plusieurs choses :

        a) Les performances, qui sont vraiment critiques dans mon métier.
        b) Les librairies. Toutes les libraires utiles dans mon métier sont écrites en C++, sans interface C.
        c) L'historique, l'outil actuel est écrit en C++ et on ne peut pas tout réécrire from scratch.
        d) Les gens formés. Trouver quelqu'un qui sait aligner deux lignes de C++, c'est facile (même si il le fait sans doute mal, il le fera). Trouver quelqu'un qui sait aligner deux lignes de Rust / Haskell / OCaml / …, c'est bien plus dur.

        Mais clairement, sur un projet neuf, où il n'y a pas autant de librairies métier à gérer et où les performances ne sont pas autant un problème, j'aimerais faire un autre outil.

        Sinon il y a quand même un truc que j'aime en C++, c'est les "int" dans les templates. Cela permet de faire un tout petit peu de typage dépendant et ça c'est marrant ;)

        • [^] # Re: Donc pour résumer…

          Posté par . Évalué à 0.

          Merci, tu confirmes tout ce que plusieurs personnes avant toi ont pu dire dans ce fil de discussion. :)

        • [^] # Re: Donc pour résumer…

          Posté par . Évalué à 3.

          L'absence de programmation générique. J'aimerais c'est pouvoir automatiquement dériver des comportements pour mes classes. À la louche, l'itération, l'affichage, la sérialisation, la comparaison, les relations d'ordre, le hash, l'enumeration, … Je recode ces trucs là pour presque chaque classe et c'est du temps perdu, du code en plus, des tests en plus, des bugs en plus, de la maintenance en plus.

          As-tu des exemples de langage dans lesquels ce que tu fais manuellement est fait automatiquement ? J’ai un peu de mal à suivre où tu veux en venir.

          Un système de type limité et hyper verbeux ce qui fait que on essaye de ne pas créer de nouveau type parce que c'est complexe. Je le retrouve tous les jours au travail ou en formation, un développeur C++ est heureux quand il a une classe suffisamment générique pour traiter tous ses cas, alors que moi je serais heureux quand j'aurais 100 classes suffisamment spécifiques pour traiter tous mes cas avec une bonne sécurité. Mais je ne le fais pas parce que maintenir 100 classes en C++ versus une, c'est l'horreur. Faire de la délégation en C++ c'est lourd.

          Euh, non, le découpage en petites unités fonctionnelles est très présent en C++. C’est ce que tout le monde recommande aujourd’hui (loi de Déméter, à l’esprit de tous les devs c++ compétents). C’est parce qu’il y a deux fichiers que c’est « trop compliqué de créer un classe » ? C’est un problème d’environnement de dev dans ce cas, pas de langage.

          Les valeurs par défaut. Beaucoup de classes de la STL imposent que votre type soit default constructible, comme par exemple le deleter de std::unique_ptr. Cela force à mettre des constructeur par défaut complètement abscons à beaucoup d'endroits.

          Euh, non. Tu peux très bien mettre un type non default constructible dans un unique_ptr. Depuis c++11, on peut même dans un vector, suivant comment on l’utilise (tant qu’on n’utilise pas resize). En plus, comme on peut le defaulter quand ça a du sens, ce n’est franchement pas la mer à boire.

          Tout est mutable par défaut ;( Là c'est clairement un point subjectif de ma part, mais je n'aime pas.

          À mettre en balance avec le fait que C++ offre un const vérifié statiquement. Bien utilisé, c’est beaucoup de bugs ou de prises de tête en moins.

          La communauté. N'allez surtout pas sur IRC si vous n’êtes pas un dieu du C++, vous vous ferez allumer et traiter d’incompétent. Expérience personnelle que j'ai mal vécue : je suis aller sur #C++ sur irc.freenode.net pour discuter une subtilité du langage qui m'avait étonnée et on m'a envoyer paître en me traitant d’incompétent qui ne comprenait pas la philosophie du langage.

          Je ne suis jamais allé sur freenode, donc je ne peux pas dire. En revanche, la communauté française est très ouverte.

          Pour le reste de tes critiques, je suis assez d’accord (sauf le pattern matching dont je trouve qu’à part l’effet trocool, ça ne sert pas à grand chose). Mais la question qui est toujours en suspens, c’est quelle alternative à c++ ? Pour du dev jetable, plein. Pour un truc censé durer 10 ans (c’est pas si long que ça dans le domaine du développement logiciel), il y a c# et java, si on peut se permettre le GC. Sinon…

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

          • [^] # Re: Donc pour résumer…

            Posté par (page perso) . Évalué à 7.

            Attention, je me sers d'Haskell dans ce post pour donner des exemples, mais je ne veux pas me lancer dans un débat C++ versus un autre langage.

            As-tu des exemples de langage dans lesquels ce que tu fais manuellement est fait automatiquement ? J’ai un peu de mal à suivre où tu veux en venir.

            En haskell, un type Square qui peut s'afficher, se serializer, se comparer à un autre, tester l'égalité et se hasher.

            > data Square = Square Float deriving (Show, Serialize, Ord, Eq, Hashable, Generic)
            > s = Square 10
            > show s
            "Square 10.0"
            > encode s
            "A \NUL\NUL"
            > s == Square 10
            True
            > s == Square 11
            False
            > s < Square 10
            False
            > s < Square 11
            True
            > hash s
            839657739146560044

            Et j'en passe bien d'autres. Tu peux bien évidement surcharger toi même le comportement si tu le désires, mais les comportement par défaut de Show, Serialize, Eq et Hashable me conviennent parfaitement. Je ne sais pas faire cela en C++. Note que ce n'est pas quelque chose écrit en dure dans le compilateur, tu es libre de crée toi même tes propres "classes" génériques (par contre c'est pas forcement là où haskell est beau ;)

            Euh, non, le découpage en petites unités fonctionnelles est très présent en C++. C’est ce que tout le monde recommande aujourd’hui (loi de Déméter, à l’esprit de tous les devs c++ compétents). C’est parce qu’il y a deux fichiers que c’est « trop compliqué de créer un classe » ? C’est un problème d’environnement de dev dans ce cas, pas de langage.

            Cela a beau être faisable, ce n'est pas ce qui est fait dans BEAUCOUP de base de code que j'ai observé (libre ou pas). Mais cela n'est pas le point.

            Je ne parle pas de découper en petites unités fonctionnelles, heureusement, c'est la base. Je parle de faire autant de petites unités fonctionnelle qu'il existe de cas de figure, pour se garantir de ne pas mélanger des tortues et des rats.

            Un exemple. Tu as un type template template <typename T> class Vector3 dont tu veux te servir pour representer des positions, des directions, des couleurs (dans différents espaces), avec différents opérations possibles. Plusieurs solutions, très souvent celle que je rencontre c'est d'utiliser la même classe pour representer tout cela. C'est ce que je reproche, car j'aimerais un type par possibilité pour éviter des opérations du genre point + couleur. Hors en C++ cela devient très vite lourd de crée un nouveau type qui ne fait que déléguer. (Note que pour ce problème tu peux t'en sortir facilement avec des types phantom, mais ce n'est pas toujours le cas).

            Euh, non. Tu peux très bien mettre un type non default constructible dans un unique_ptr. Depuis c++11, on peut même dans un vector, suivant comment on l’utilise (tant qu’on n’utilise pas resize). En plus, comme on peut le defaulter quand ça a du sens, ce n’est franchement pas la mer à boire.

            Tu ne peux pas mettre un non default constructible comme deleter de unique_ptr. (i.e.: le second argument du template), même dans le cas où ton unique ptr est default initialisé avec nullptr. Ce qui te force à crée un constructeur par défaut à ton deleter et à gérer ce cas. Ce n'est pas la mer à boire je te l'accorde ;)

            À mettre en balance avec le fait que C++ offre un const vérifié statiquement. Bien utilisé, c’est beaucoup de bugs ou de prises de tête en moins.

            Je suis d'accord avec toi. J'aurais aimé l'inverse, du const par défaut et un truc genre mutable pour ce qui peut changer.

            Pour le reste de tes critiques, je suis assez d’accord (sauf le pattern matching dont je trouve qu’à part l’effet trocool, ça ne sert pas à grand chose).

            Disons que sans type algébrique, le pattern matching ne sert en effet pas à grand chose. Mais quand tu as des types algébriques et un pattern matching en profondeur, il permet d'expliciter tous les cas à traiter, avec une garantie statique à la compilation que tu as bien tout traité et une grande lisibilité de code.

            Mais la question qui est toujours en suspens, c’est quelle alternative à c++ ? Pour du dev jetable, plein. Pour un truc censé durer 10 ans (c’est pas si long que ça dans le domaine du développement logiciel), il y a c# et java, si on peut se permettre le GC. Sinon…

            Je n'ai jamais prétendu proposer une alternative au C++, d'ailleurs, comme le disait, écrire du C++ c'est ce qui me paye et j'y trouve certain avantage (au c++, et aussi au fait que on me paye ;)

            Je n'ai pas l’expérience pour te répondre sur la pérennité du truc. J'ai vraiment tendance à penser que des langages avec des outils de packaging comme ce que on trouve en Rust ou Haskell vont te permettre de rebuilder ton projet dans les même conditions dans 10 ans, alors que ressortir un projet C++ qui a 10 ans et essayer de le rebuilder, c'est toujours une horreur de cache cache avec les dépendances. Pour moi c'est aussi une grande faiblesse du C++, c'est que, sauf si ta recette de build inclue TOUTES les dépendances ainsi que le compilateur, tu n'es pas garanti d'avoir un build identique 6 mois plus tard.

          • [^] # Re: Donc pour résumer…

            Posté par . Évalué à 3.

            Euh, non, le découpage en petites unités fonctionnelles est très présent en C++

            Je pense qu'il veut juste dire que c'est très chiant de faire ça. C++ est un langage hyper-verbeux, et je comprends bien l'impression qu'on peut avoir à passer des jours à écrire des classes quasi-vides et/ou quasi-identiques pour avoir un truc cohérent. Si tu pousses la logique de l'IDE comme aide à la programmation, il te faudrait presque un générateur de code (et du coup, tu vas finir par réinventer un langage au dessus du langage).

            • [^] # Re: Donc pour résumer…

              Posté par . Évalué à 1.

              Et si du code peut être généré automatiquement, c'est qu'il y a un problème au niveau du langage, qui devrait te donner la possibilité de ne pas écrire ce code. Moins de code, moins d'erreurs, moins de temps pour faire le code, moins de temps pour relire, moins de temps pour l'audit, moins de maintenance.

              J'ai le même problème avec Java.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 3.

                J'ai le même problème avec Java.

                Qu'est-ce que tu génère en Java ? (pure curiosité)

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

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 1.

                  Ce que je générais (car, bien heureux je suis, je n’ai plus à coder en Java :)) était les get/set. Je sais que cette fonctionnalité est très utilisée dans les IDE, je l’avais plus ou moins reproduite dans mon Vim.

              • [^] # Re: Donc pour résumer…

                Posté par . Évalué à 2.

                c'est qu'il y a un problème au niveau du langage, qui devrait te donner la possibilité de ne pas écrire ce code.

                Sur le fond, c'est pas faux, mais le risque est d'inventer des syntaxes obscures pour éviter d'écrire du code, et ça peut être presque pire.

                L'exemple typique qui me vient en tête, c'est une classe où un seul membre ne doit pas être recopié tel quel. Il faut se farcir un constructeur de copie et un opérateur d'affection au code trivial et dupliqué, y compris dans les classes filles ; des centaines de lignes avec des bugs ou des fautes de frappe potentielles, très peu évolutifs, pour un truc aussi con que "copie tous les membres sauf un". Il y a des dizaines de cas similaires en C++, où on pisse des km de code trivial (getters/setters pour remplacer un mécanisme de permissions en lecture/écriture, par exemple).

                Mais imagine que le comité de normalisation accepte de considérer un problème en particulier. La solution va être de founir une nouvelle syntaxe, de nouveaux mots-clé, des nouveaux comportements indéfinis, des nouvelles constructions obscures qui risquent d'entrer en collision avec des syntaxes existantes—finalement, la solution risque d'être bien pire que le mal, non?

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  Dans le cas du C++, oui. Bien entendu, il faut gérer l’existant et du coup toute tentative de simplification devient une tâche… complexe.

                  Mais dans d’autres langages, tu n’as pas cette complexité, redondance, lourdeur inutile. Et c’est un élément qui me fait les préférer.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 3.

                  Sur le fond, c'est pas faux, mais le risque est d'inventer des syntaxes obscures pour éviter d'écrire du code, et ça peut être presque pire.

                  Oui et c'est assez marrant, en go c'est assez courant de voir de la génération de code.

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

                • [^] # Re: Donc pour résumer…

                  Posté par (page perso) . Évalué à 2.

                  "copie tous les membres sauf un"

                  Il pourrait y avoir une class dans une bibliothèque. Un truc du genre: (non testé)

                  template<typename T> class  no_copy {
                     T value;
                  public:
                     no_copy(const T&v) : value(v) {}
                     no_copy(const no_copy&) : value() {} // zero-initialize
                     operator T&() { return value;  }
                  };
                  // Move sementic, const correctness and operator =, *, ->, ->*,  are left as an exercise to the reader.

                  Et ensuite dans ta classe:

                  struct FooBar {
                     int normalMember1;
                     std::string normalMember2;
                     no_copy<int> memberThatShouldNotBeCopied;
                  };

                  (getters/setters pour remplacer un mécanisme de permissions en lecture/écriture, par exemple).

                  On pourrait imaginer un read_only<T, U> dont les opérations non constantes seraient privée et qui aurait un friend U.

                • [^] # Re: Donc pour résumer…

                  Posté par . Évalué à 2.

                  Je viens de réaliser un truc en relisant :

                  L'exemple typique qui me vient en tête, c'est une classe où un seul membre ne doit pas être recopié tel quel. Il faut se farcir un constructeur de copie et un opérateur d'affection au code trivial et dupliqué, y compris dans les classes filles

                  Si ta classe fait partie d’une hiérarchie polymorphe, elle ne devrait pas être copiable. Sinon, tu t’assures de faire de la merde et des bugs à un moment où un autre.

                  Si tu pars sur une hiérarchie non polymorphe (typiquement, avec CRTP), il est possible de factoriser le tout (en délégant la copie de la partie concernée de ta structure).

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

                  • [^] # Re: Donc pour résumer…

                    Posté par . Évalué à 2.

                    Si ta classe fait partie d’une hiérarchie polymorphe, elle ne devrait pas être copiable. Sinon, tu t’assures de faire de la merde et des bugs à un moment où un autre.

                    Par copiable, tu entends par l'opérateur d'affectation ou par le constructeur de copie? Ça m'est déja arrivé de faire des fonctions virtual Base * clone(const Base ) = 0; qui sont explicitées dans les classes filles en appelant le constructeur de copie par un new(this), et ça ne m'avait pas choqué.

                    Si j'ai bonne mémoire, si tu essayes de faire *Base = *Derived; en appelant l'opérateur d'affectation, ça doit compiler. Il me semble avoir déja vu des contextes où ça marchait et où ça avait du sens.

                    en délégant la copie de la partie concernée de ta structure

                    Tu veux dire de définir une struct connue de ta classe seule qui contiendrait tous les membres copiables de la classe, ce qui permettrait de pouvoir copier tous les membres d'un coup? Ça ne fonctionne que si tu n'as pas une hiérarchie qui ajoute des membres, autrement c'est presque pire.

                    • [^] # Re: Donc pour résumer…

                      Posté par . Évalué à 2.

                      Par copiable, tu entends par l'opérateur d'affectation ou par le constructeur de copie? Ça m'est déja arrivé de faire des fonctions virtual Base * clone(const Base ) = 0; qui sont explicitées dans les classes filles en appelant le constructeur de copie par un new(this), et ça ne m'avait pas choqué.

                      Les deux. L’usage d’une fonction clone est problématique aussi (qu’est censé faire Derived::clone(Base const& b) si on lui donne un Base& qui est de type Derived2 ? Un assert ? pas terrible…).

                      Copier la partie Base d’un Derived un autre Base peut avoir du sens, en revanche, copier dans un Derived une partie Base en a beaucoup moins. Et je ne sais pas autoriser l’un sans autoriser l’autre.

                      Il y a des cas où on en arrive à faire ça (une hiérarchie de classes copiables), mais en général par soucis de simplificité (comprendre, flemme :) ) et c’est source de problèmes. Ce n’est pas quelque chose de conceptuellement très correct.

                      Tu veux dire de définir une struct connue de ta classe seule qui contiendrait tous les membres copiables de la classe, ce qui permettrait de pouvoir copier tous les membres d'un coup? Ça ne fonctionne que si tu n'as pas une hiérarchie qui ajoute des membres, autrement c'est presque pire.

                      Ce n’est pas du tout à ça que je pensais. Je ne vois pas trop comment faire un exemple simple, mais l’idée, c’est plutôt de passer par un mécanisme de copie non-standard (appel de fonction statique, pex) pour se prémunir d’un usage imprévu / susceptible de poser des problèmes (à la place d’une copie implicite potentiellement problématique, tu as une erreur de compilation, c’est beaucoup mieux).

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

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 5.

                        Quand je lis ce genre de commentaire, je suis encore plus convaincu que le C++ n’est pas simple. Même des personnes travaillant avec depuis assez de temps pour comprendre ce genre de discussion ne semblent pas aptes à produire du code sans qu’il n’y ait des problèmes de cohérence, et des erreurs potentielles à la clé.

                        • [^] # Re: Donc pour résumer…

                          Posté par (page perso) . Évalué à 4.

                          C'est pareil dans chaque language.

                          En JavasSript par exemple, langage réputé plus "simple", quelqu'un pourrait essayer de savoir si un object a une propriété particulière

                          if (obj[prop]) doSomething();

                          On lui répondra que non c'est pas comme ça qu'il faut faire, il faut faire

                          if (typeof(obj[prop] != "undefined")
                             doSomething();

                          Mais certains iront lui dire que il doit utiliser !== et non != et que il n'as pas besoin d'utiliser de ; en javascript parce que c'est moche. Mais d'autres diront que il faut utiliser des ; après tout car c'est plus correct et que ça évite certain problèmes.

                          Mais en fait ça ne marche pas si la propriété en question est héritée depuis le prototype. Donc il faut faire.

                          if (obj.hasOwnProperty(prop))

                          Mas non, c'est pas suffisant, il faut en fait faire

                          Object.prototype.hasOwnProperty.call(obj, prop)

                          Enfin ce que je veux dire c'est que les piailleurs aiment pinailler. Et que on peu faire ça avec chaque langage.
                          Le fait qu'il y ait des pinailleurs ne veux pas dire grand chose sur la qualité d'un langage.

                        • [^] # Re: Donc pour résumer…

                          Posté par . Évalué à 1.

                          Tu peux aussi remplacer C++ par développement en général. La conception logicielle, ce n’est pas simple, les APIs pourries ou les fautes de conception il y en a dans tous les langages.

                          C’est aussi pour ça que C++ évolue lentement : pour se prémunir d’erreurs dans le standard (c’est boost qui essuie les plâtres).

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

                          • [^] # Re: Donc pour résumer…

                            Posté par . Évalué à 4.

                            C'est quand même un chouilla plus complexe. Le C++ est un langage très souple, et on peut en faire à peu près n'importe quoi tant qu'on respecte la syntaxe de base. Par exemple, rien n'oblige à utiliser des classes, ni à utiliser la STL. Inversement, des mécanismes pourtant basiques ne sont pas inclus dans le standard, par exemple les directives préprocesseur pour éviter d'inclure plusieurs fois les headers.

                            Bref, un langage ne peut pas te prémunir contre le fait de coder n'importe comment. Ce qu'on peut reprocher au C++, c'est de ne même pas définir la manière correcte de faire les choses, ce qui fait qu'il n'est jamais clair si on fait les choses correctement ou non. Surtout qu'une manière correcte de faire va devenir incorrecte avec l'évolution du standard, ce genre de choses.

                            • [^] # Re: Donc pour résumer…

                              Posté par (page perso) . Évalué à 0.

                              Bref, un langage ne peut pas te prémunir contre le fait de coder n'importe comment. Ce qu'on peut reprocher au C++, c'est de ne même pas définir la manière correcte de faire les choses, ce qui fait qu'il n'est jamais clair si on fait les choses correctement ou non.

                              C'est aussi un avantage de pouvoir faire les choses comme on veut, sans être forcé à suivre les choix des concepteurs du langage. C'est ce qui permet à C++ d'évoluer au cours du temps, en apprenant de ses erreurs.

                              D'autres langages font des choix plus tranchés et ça les rend moins polyvalents, dans différents domaines d'application ou au cours du temps. Le C++ s'adapte et permet de suivre les tendances (même s'il ne suit que de loin), là ou d'autres langages suivent le concept à la mode, puis finissent par vieillir et disparaître, remplacés par un nouveau langage "moderne", avec le dernier concept à la mode.

                              • [^] # Re: Donc pour résumer…

                                Posté par . Évalué à 5.

                                C'est aussi un avantage de pouvoir faire les choses comme on veut, sans être forcé à suivre les choix des concepteurs du langage.

                                Il y a une différence entre "être forcé de suibre les choix des concepteurs" et être guidé vers les bonnes pratiques.

                                Prend par exemple le char** dans le main(). C'est quand même super paradoxal, non? Ça ne semblerait pas plus cohérent de pouvoir obtenir un std::vector<std::string>? Le langage envoie des signaux contradictoires tout le temps.

                                • [^] # Re: Donc pour résumer…

                                  Posté par . Évalué à 4.

                                  Il y a une différence entre "être forcé de suibre les choix des concepteurs" et être guidé vers les bonnes pratiques.

                                  C’est quelque chose du genre, que tu cherches ?

                                  Prend par exemple le char** dans le main(). C'est quand même super paradoxal, non? Ça ne semblerait pas plus cohérent de pouvoir obtenir un std::vector<std::string>? Le langage envoie des signaux contradictoires tout le temps.

                                  main fait partie de l’API C. Passer un std::vector<std::string> impliquerait :
                                  * de forcer à compiler avec la STL (donc support des exceptions, rtti, exécutable plus gros, etc).
                                  * un overhead au démarrage lié à la construction de ce vecteur à partir de l’API C.

                                  Ce serait donc contraire à la philosophie C++ de ne payer que pour ce qu’on utilise.

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

                              • [^] # Re: Donc pour résumer…

                                Posté par . Évalué à 4.

                                D'autres langages font des choix plus tranchés et ça les rend moins polyvalents, dans différents domaines d'application ou au cours du temps.

                                LE langage connu pour ça c'est python. Moins polyvalent tu dis ? ^^

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

                                • [^] # Re: Donc pour résumer…

                                  Posté par (page perso) . Évalué à 1.

                                  Mon test facile pour ça: est-ce qu'on peut écrire un OS entier avec?

                                  Mais j'admet volontier que je fais beaucoup d'informatique embarquée, et moins de certains autres trucs qui sont possibles en Python mais pas en C++ (d'ailleurs je ne vois même pas d'exemple, là comme ça)

                                  • [^] # Re: Donc pour résumer…

                                    Posté par . Évalué à 2.

                                    d'ailleurs je ne vois même pas d'exemple, là comme ça

                                    Tout ce qui est big data (c'est potentiellement possible, mais je ne connais pas d'écosystème le permettant), tout ce qui est serverless (en fait on peut mais c'est vraiment pas fais pour), tout ce qui est DSL, tout ce qui est embarquer du code sous à chaud (comme les extensions de navigateur par exemple), tout ce qui s'exécute au sein d'une page web (si tu transpile tu triche parce qu'on peut transpiler n'importe quoi en n'importe quoi),…

                                    C'est dommage de voir l'OS comme le saint Graal qui définirait le summum de la programmation. Il y a un paquet de problématique qui sont tout aussi compliquée. C'est juste une façon biaisée de dire que tu fais du système.

                                    Mais si tu veux, on peut parler de go qui est directif et permet d'écrire des OS, je pense. Il suffit de ne pas avoir de runtime séparé et de pouvoir manipuler un peu violement de la mémoire (ou au pire s'interfacer avec un peu de C ou d'assembleur pour ça, non ?).

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

                                    • [^] # Re: Donc pour résumer…

                                      Posté par . Évalué à 2.

                                      Mais si tu veux, on peut parler de go qui est directif et permet d'écrire des OS

                                      AFAIK, Go utilise un GC et a donc besoin d'un runtime en dessous. Alors techniquement parlant, on pourrait aussi réécrire le runtime en bare-metal (je suppose ?), mais dans ce cas, je pense qu'on pourrait écrire un OS avec pratiquement n'importe quel langage.

                                      • [^] # Re: Donc pour résumer…

                                        Posté par . Évalué à 3.

                                        Le gc de go est bare metal. Tout est inclus dans le binaire qui est directement exécuté sur la cpu. Amuse toi à écrire un hello world, le cross compiler pour Windows ou Linux arm, déplacer le binaire obtenu et l'exécuter simplement sur la machine cible. Si tu as déjà essayé de faire ça en C, c'est le jour et la nuit.

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

                                        • [^] # Re: Donc pour résumer…

                                          Posté par . Évalué à 0.

                                          OK, merci pour la correction.

                                        • [^] # Re: Donc pour résumer…

                                          Posté par . Évalué à 2.

                                          C'est surtout une conséquence du fait que Go link tout par défaut non? En C en linkant statiquement contre musl on doit pouvoir obtenir le même comportement.

                                          • [^] # Re: Donc pour résumer…

                                            Posté par . Évalué à 4.

                                            Si tu parle du fait de prendre un binaire fraîchement construit, le copier sur la machine cible et l'exécuter oui.
                                            Par contre la génération de ce dernier est nettement plus simple avec go (c'est l'histoire de 2 variables d’environnement)

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

                                    • [^] # Re: Donc pour résumer…

                                      Posté par (page perso) . Évalué à 1.

                                      Merci pour ces exemples.

                                      Je prend le cas du système d'exploitation pour deux raisons, d'abord parce que je le connaît bien, et d'autre part parce que ça couvre déjà pas mal de choses: noyau, drivers, librairies, applications.

                                      Mais on est d'accord, j'ai une vue très "système" sur les choses, et c'est ça qui fait de C++ un bon choix pour moi. Certainement pas un langage universel.

                                      Pour la transpilation, ça ne me paraît pas être un problème (pas plus que la compilation). Mes interrogations portent sur le langage, plutôt que sur la façon dont il est exécuté. Par exemple, pour faire de la programmation système en Java, il y a plein de problèmes lié au langage lui-même. Au hasard, pas de pointeurs et donc pas de moyen d'accéder directement à une adresse mémoire, donc pas possible d'écrire un driver; le langage a besoin d'un garbage collector qui peut difficilement être écrit en Java, etc.

                                      Cela dit, il y a d'autres raisons qui font que la transpilation rend les choses pénibles: c'est chiant à debugger, c'est moins pratique pour développer les choses, etc. Et donc, on a bien besoin d'un ou plusieurs langages complémentaires pour ces cas là, merci pour les exemples!

                                      • [^] # Re: Donc pour résumer…

                                        Posté par . Évalué à 3.

                                        Pour la transpilation[…]

                                        J'ai parlé de transpilation uniquement pour dire qu'écrire du C++, le transpiler avec ASM.js en js pour l'exécuter dans un navigateur ne rend pas véritablement le C++ embarquable dans un navigateur :)

                                        Sinon avec python et pythran, je peux écrire un OS ^^

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

                                  • [^] # Re: Donc pour résumer…

                                    Posté par . Évalué à -2.

                                    Mon test facile pour ça: est-ce qu'on peut écrire un OS entier avec?

                                    Tant que c'est Turing complet, oui…

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

                                    • [^] # Re: Donc pour résumer…

                                      Posté par (page perso) . Évalué à 2.

                                      Euh non, ça n'a pas trop de rapport.

                                      Un OS ce n'est pas que du calcul, c'est aussi beaucoup de gestion des ressources, ce qui n'a rien à voir avec Turing.

                                      • [^] # Re: Donc pour résumer…

                                        Posté par . Évalué à -1.

                                        Et on peut gérer des ressources en Python, Ruby, etc ?

                                        Ben merde alors.

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

                                        • [^] # Re: Donc pour résumer…

                                          Posté par . Évalué à 3.

                                          Qu'entends-tu par « gérer des ressources » ? En python le mot clef with permet de faire de l'acquisition/libération de ressource se rapprochant de très loin de la RAII. En ruby je sais pas. En java tu peut très bien travailler hors de la heap (et tu as le try-with-resources pour faire comme le with de python).

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

                                        • [^] # Re: Donc pour résumer…

                                          Posté par (page perso) . Évalué à 5.

                                          Ce que tu oublies, ou plutôt que la notion de Turing complet, ne met pas en exergue, c'est que ton langage peut être Turing complet mais ne pas pouvoir tel quel faire n'importe quel programme informatique.

                                          Typiquement, je crois que SQL est Turing complet dans certaines version, tu ne pourras pas créer des pages Web, dessiner des interfaces graphique ou faire un OS en te reposant uniquement sur ce langage.

                                          Un système d'exploitation, surtout le noyau, demande de faire des choses assez inhabituelles que de nombreux langages ne peuvent pas faire naturellement. Par exemple, faire de l'assembleur, accéder à une zone mémoire physique précise (registres de composants ou une adresse mémoire particulière).

                                          Certains langages sont de toute façon assez inaptes à faire cela car ils dépendent par exemple d'un Garbadge Collector non prédictible (que ce soit le moment où il s'exécute comme la durée) ce qui n'est pas souhaitable lors du traitement d'interruptions par exemple.

                                          Beaucoup de langages, surtout les langages jouets ou petits, ne peuvent pas non plus faire certaines applications car ils n'ont aucune possibilité d'utiliser l'API du système sur lequel ils sont.

                                          Donc si d'un point de vue théorique la notion de Turing complet permet de tout faire, en pratique cela n'est pas vrai. Car notre ordinateur n'est pas une vraie machine de Turing d'une part (la RAM n'est pas infinie), et que pour exploiter la machine il y a des contraintes que n'a pas la machine théorique.

                                        • [^] # Re: Donc pour résumer…

                                          Posté par (page perso) . Évalué à 1.

                                          Je ne vois pas ce que tu veux dire, à part sans doute de méprendre complètement sur le sens de Turing-complet. Qu'un langage soit capable de calculer toute fonction effectivement calculable (n'a strictement rien à voir avec le boulot d'un OS qui gère aussi des entrées sorties et qui a des contraintes de perf.

                            • [^] # Re: Donc pour résumer…

                              Posté par . Évalué à 1.

                              des mécanismes pourtant basiques ne sont pas inclus dans le standard, par exemple les directives préprocesseur pour éviter d'inclure plusieurs fois les headers

                              Absolument rien n'est basique dans ton exemple. L'existence de liens symboliques et durs fait que ton #pragma once peut te péter à la gueule sans crier gare, sans parler de deux fichiers identiques qui seraient dans deux endroits différents. Bref, si ce n'est pas standardisé, c'est que ces problèmes ne pourront sans doute jamais être résolus de manière sûre. D'autant plus que s'il y a des include guard, les compilateurs sont suffisamment intelligents pour ne pas les réinclure dans les bons cas.

                              • [^] # Re: Donc pour résumer…

                                Posté par . Évalué à 6. Dernière modification le 04/10/16 à 15:51.

                                C'est pas un problème que quelque chose d'aussi courant soit si complexe ? Sans qu'on est pas dans un problème que personne n'a jamais sut résoudre, hein ?

                                J'aime bien personnellement quand les choses courantes sont simple et que ce soit les choses complexes qui représentent un enjeu. Ça ne donne vraiment pas envie, de voir des choses courantes devenir si complexes quand on essaie de les faire en C++.

                                Désolé d'être médisant mais c'est vraiment l'effet que ça donne. Là où tous les autres langages ajoutent des fonctionnalités sereinement dès qu'il en est question en C++, on voit un « non, mais attends tu ne te rend pas compte de comment c'est compliqué… ».

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

                                • [^] # Re: Donc pour résumer…

                                  Posté par . Évalué à 5.

                                  C'est pas un problème que quelque chose d'aussi courant soit si complexe

                                  Bien sûr que si, et je trouve qu'il y a un peu de mauvaise foi dans les réponses du type "ouhlalala, ça restreindrait la liberté de l'utilisateur et ça biaiserait la philosophie du langage si on faisait ça". La gestion des en-tête en C++ est assez ridicule, c'est au programmeur de bricoler des directives au pré-processeur tout ça parce que le langage n'est pas fichu de reconnaitre un fichier de header. Ça gênerait la liberté de qui exactement si le langage était capable de reconnaitre la stucture de 99.9999% des programmes en C++? Il y a des gens qui "#include" des trucs autres que des .h?.

                                  Avec des raisonnements comme ça, on ne change jamais rien. La liberté de l'utilisateur a bon dos… Y compris dans la justification de la non-dépréciation de syntaxes obsolètes qui n'ont aucune justification (goto, void*, etc). Il y a du code de 40 ans qui compile grâce à ces trucs? Qu'est-ce qu'on s'en fout, il est en général indispensable d'adapter le code de l'année d'avant quand on change de version de gcc de toutes manières…

                                  • [^] # Re: Donc pour résumer…

                                    Posté par . Évalué à 2.

                                    Qu'est-ce qu'on s'en fout, il est en général indispensable d'adapter le code de l'année d'avant quand on change de version de gcc de toutes manières…

                                    C’est avec ce genre de remarques que tu décrédibilises totalement ton propos. S’il y a effectivement du code qui peut être cassé à chaque évolution du langage, on est très loin de ce que tu décris et de la fréquence que tu sembles indiquer.

                                    Peut-être justement parce que :

                                    Y compris dans la justification de la non-dépréciation de syntaxes obsolètes qui n'ont aucune justification (goto, void*, etc). Il y a du code de 40 ans qui compile grâce à ces trucs?

                                    oui. Et qu’en plus, on veut pouvoir compiler la même base de code avec le dernier compilateur moderne et aussi la vieille antiquité d’il y a 10 ans qui compile pour notre microcontrôleur spécifique, de préférence sans avoir à faire des #ifdef dans tous les sens.

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

                                    • [^] # Re: Donc pour résumer…

                                      Posté par . Évalué à 5.

                                      S’il y a effectivement du code qui peut être cassé à chaque évolution du langage, on est très loin de ce que tu décris et de la fréquence que tu sembles indiquer.

                                      Les évolutions du langage, c'est tous les 2 ou 3 ans. Et ça m'arrive très souvent de passer du temps à rebricoler des trucs pour compiler du vieux code, simplement parce que les compilateurs corrigent des bugs d'implémentation de la norme (des trucs qui émettaient un warning et qui donnent une erreur, des syntaxes illicites qui passaient mais qui ne passent plus). Évidemment, pour un projet à soi, on s'en fout (une demi-journée tous les trois ans, ça n'est pas la mer à boire), mais pour l'ensemble de l'écosystème, c'est juste chiant. Et du coup, l'idée de compiler du vieux code non-trivial sans mettre les mains dans le cambouis, ça ne fonctionne pas. Alors un truc dégueu (genre goto) qui devient déprécié, ça gène qui exactement? Surtout que tu pourrais avoir une option du compilo pour dire "compile-moi ça quand même".

                                      Le problème, c'est que C++ ne veut pas choisir entre la rétrocompatibilité C et le C with classes d'un côté, et un langage objet moderne de l'autre côté. Est-ce qu'on peut promouvoir indéfiniment ce modèle chimérique? Ça intéresse qui exactement de pouvoir faire un printf avec un argument template? Évidemment, tu peux lire des bouquins, des guidelines, des tutoriels, mais si le langage te transmets l'idée qu'il y a 75 manières de faire quelque chose et qu'il est neutre à ce sujet, il y a un problème. Si la cohérence du language tient au respect de quelques bonnes pratiques qui ne sont pas implémentées par défaut, alors il y a un problème (par exemple, pourquoi les destructeurs ne sont-ils pas virtuels par défaut?).

                                      Sur le fond, la question n'est pas sur la complexité de certaines techniques très poussées qui ne servent qu'à une toute petite fraction des développeurs. C'est peut-être cool de pouvoir surcharger "new" ou "*", personnellement je m'en fous. Par contre, il est anormal que cette souplesse s'accompagne de bizarreries historiques portant sur du code complètement courant (typiquement, les arguments de main), des syntaxes WTF, ou des subtilités pédantesques (personnellement, j'aime bien le comportement d'un bool qu'on a oublié d'initialiser).

                                      • [^] # Re: Donc pour résumer…

                                        Posté par . Évalué à 2.

                                        (par exemple, pourquoi les destructeurs ne sont-ils pas virtuels par défaut?)

                                        Parce que la présence d'une méthode virtuelle implique la génération d'une vtable, qui a un impact sur les performances.

                                        typiquement, les arguments de main

                                        Portabilité & Compatibilité (oui, je sais) avec la famille execXY() standardisée par POSIX (ne pas oublier que la portabilité est importante pour les utilisateurs de C++).

                                        Tu proposes quoi?
                                        De renvoyer un bool et d'avoir un std::vector<std::string>const& en entrée?

                                        Ça implique argc + 1 appels à new (puisqu'il faudrait copier ce que l'OS envoie, ces conteneurs n'étant pas capables de prendre la propriété d'un pointeur) pouvant throw en cas de problème mémoire. Et ou tu catch, puisque ça à throw avant même d'entrer dans le code?

                                        Je ne mentionne pas le fait que std::string, c'est potentiellement 24 octets par objet (en plus du contenu, tu as en gros un pointeur vers les données, un vers la fin de la zone utilisable et un dernier qui indique la fin réelle. 3 * 8 sur une machine 64bits classique).
                                        Si t'as en entrée un truc genre apt-cache pkgnames, ça risque de faire pas mal de mémoire (45237 entrée chez moi, autrement dit plus d'1Mio potentiels une fois *24. Je dis potentiels, parce qu'avec les SSO ça peut être moins, en fonction du compilo.) et de risques de throw, le tout pour faire plaisir à des gens qui n'ont rien à faire de la performance et de la portabilité, qui ne sont pas la cible première de C++?
                                        Quel réel avantage y aurait-il? Au passage, std::basic_string<char> ou std::basic_string<wchar> (bon, j'ai un doute, je sais plus si c'est wchar ou wchar_t…)?

                                        (personnellement, j'aime bien le comportement d'un bool qu'on a oublié d'initialiser)

                                        Au moins n'est-il pas null, nil, nul ou whatever truc qu'on peut voir ailleurs. Ici, tu oublies un truc: en C++ on ne paye pas pour ce qu'on n'utilise pas. Et ce n'est pas toujours utile d'initialiser tes variables quand tu les déclares: par exemple une variable qui sera initialisée dans une boucle, et utilisée après la fin de cette boucle, il n'y a aucune raison de l'initialiser avant.
                                        Au passage, ton compilo te préviendra pour 99% des cas (jusqu'à présent, il m'a toujours prévenu, mais bon, je laisse place à l'incertitude).

                                        • [^] # Re: Donc pour résumer…

                                          Posté par . Évalué à 1.

                                          De renvoyer un bool et d'avoir un std::vector<std::string>const& en entrée?

                                          Je pense que dans ce cas là, on utiliserait plutôt des std::string_view (qui ont juste un pointeur et une taille, donc pas d'allocation) plutôt que des std::string. Malheureusement, il n'y a pas d'équivalent pour les tableaux.

                                          • [^] # Re: Donc pour résumer…

                                            Posté par . Évalué à 1.

                                            Il semblerait que ce ne sera possible que l'an prochain, alors que la critique est faite cette année.

                                            Ça, c'était pour la mauvaise foi de circonstance :)
                                            Je reconnaît que je ne connaissais pas le futur std::string_view (et je te remercie au passage de m'informer de son existence) mais je citais un cas présent, avec le standard existant.

                                            Toujours est-il que cette nouvelle classe semble méchamment plus intéressante que std::string, il faudra que je regarde de plus près, afin de questionner les choses que je ne comprends pas, pour évoluer au prix de quelques moinssages. Mais tel est le prix de la liberté :)

                                        • [^] # Re: Donc pour résumer…

                                          Posté par . Évalué à 2.

                                          Parce que la présence d'une méthode virtuelle implique la génération d'une vtable, qui a un impact sur les performances.

                                          Dans ce cas, le concept même de coder en C++ a un impact sur les performances. Parce que si tu n'utilises pas le polymorphisme, ni la STL, ni les exceptions, ni les templates et que tu préfères utiliser des pointeurs nus et des macros, alors ça ressemble furieusement à ce que certains appellelent du C/C++, qui n'est en fait ni du C ni du C++.

                                          Au passage, la règle pourrait bien être que les destructeurs d'une classe qui a au moins une méthode virtuelle sont virtuels. Ou de définir un mot clé "nonvirtual" pour les 0.01% des cas où on veut explicitement éviter la création d'une vtable.

                                          Portabilité & Compatibilité (oui, je sais) avec la famille execXY() standardisée par POSIX

                                          Dans ce cas, je ne vois pas le problème à surcharger la fonction main, une en int main(int, char**) et une en bool main(vector<string>). Encore une fois, si tu veux faire du C/C++, tu peux utiliser la version C, le compilo peut bien voir quelle version tu utilises.

                                          des gens qui n'ont rien à faire de la performance et de la portabilité, qui ne sont pas la cible première de C++?

                                          On pourrait débattre indéfiniment sur la cible première du C++, mais la performance et la portabilité ne peuvent pas être les deux seuls critères ; autrement, les gens feraient du C. Le C++, c'est avant tout la POO, la programmation générique, l'encapsulation, la STL, et tous les petits trucs du C++ moderne (lambda, inférence de type, shared_pointers, etc). Je t'accorde qu'il existe des gens comme toi qui interprètent le langage en tant que C amélioré, et notamment qui pondèrent l'utilisation d'un certain nombre de fonctionnalités du langage par les coûts induits en temps d'exécution et en mémoire. Mais il faut aussi voir que pour une très vaste communauté, ces choses n'ont aucun intérêt par rapport aux avantages de l'encapsulation, de la gestion des erreurs, ou des vérifications de types faites à la compilation?

                                          • [^] # Re: Donc pour résumer…

                                            Posté par . Évalué à 6.

                                            la performance et la portabilité ne peuvent pas être les deux seuls critères ; autrement, les gens feraient du C

                                            C++ est plus performant que C (à code comparable). L’exemple typique est std::sort vs qsort, parce std::sort n’a pas le coût associé au pointeur de fonction. C++ te permet de faire à la compilation des optimisations dont tu ne peux même pas rêver en C.

                                            On peut utiliser massivement les templates sans utiliser la moindre méthode virtuelle (c’est d’ailleurs à de nombreux endroits le cas dans la STL). Il existe des raisons (pas forcément les meilleures, certes) d’avoir des méthodes virtuelles dans une classe mais un destructeur non virtuel (dans ce cas, il faut théoriquement mettre le destructeur de la classe de base en protégé).

                                            Le choix par défaut de C++ est celui qui ne coûte pas. Si ton propos est de dire que le compilateur devrait et péter un gros warning quand tu as un destructeur non virtuel public dans une classe ayant une méthode virtuelle, je suis tout à fait d’accord. En revanche, s’il est que tout doit être virtuel par défaut, non. Ce n’est pas l’esprit du langage, il y en a d’autres pour ça. Don’t pay for what you don’t use.

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

                                          • [^] # Re: Donc pour résumer…

                                            Posté par . Évalué à 7.

                                            Au passage, la règle pourrait bien être que les destructeurs d'une classe qui a au moins une méthode virtuelle sont virtuels. Ou de définir un mot clé "nonvirtual" pour les 0.01% des cas où on veut explicitement éviter la création d'une vtable.

                                            Ce n'est pas 0.01% mais bien plus puisqu'en C++, les struct et les class, c'est la même chose (à la différence que dans une struct, la visibilité par défaut est publique tandis que dans une class, la visibilité par défaut est privée). Donc, toutes les structures issues de bibliothèques C (donc utilisables en C++) sont concernées. Or, en C, pas de vtable. Il est donc parfaitement impensable d'avoir comme comportement par défaut que tout est virtuel et qu'on dit ce qui ne l'est pas puisque ça briserait complètement la compatibilité avec le C.

                                            Et pour les cas où il faut un destructeur virtuel, -Wdelete-non-virtual-dtor est là pour émettre un message d'erreur si on a oublié.

                                          • [^] # Re: Donc pour résumer…

                                            Posté par . Évalué à 2.

                                            Au passage, la règle pourrait bien être que les destructeurs d'une classe qui a au moins une méthode virtuelle sont virtuels. Ou de définir un mot clé "nonvirtual" pour les 0.01% des cas où on veut explicitement éviter la création d'une vtable.

                                            Donc faire se taper un overhead inutile à 95% des appels de méthodes ?
                                            Interdire l'inlining implicite de toutes les méthodes tant qu'elles n'ont pas été explicitement marquées nonvirtual (ou final ou private), comme c'est le cas en java ?

                                            Au passage, la règle pourrait bien être que les destructeurs d'une classe qui a au moins une méthode virtuelle sont virtuels.

                                            Tous les compilos récents gueulent quand tu oublies le destructeurs virtual d'une classe qui a une/des méthodes virtual.
                                            Ce n'est donc plus la peine de complexifier le langage le problème est réglé du point de vue de la vie quotidienne du développeur (sauf celui qui compile sans warnings mais lui il veut aller en enfer).

                                            surcharger la fonction main, une en int main(int, char**) et une en bool main(vector)

                                            T'as pas l'impression de t'attaquer à un détail de détail de détail là ?
                                            Ça représente moins de 0,001 % de n'importe quel programme sérieux la signature du point d'entrée.

                                            • [^] # Re: Donc pour résumer…

                                              Posté par . Évalué à 4.

                                              Interdire l'inlining implicite de toutes les méthodes tant qu'elles n'ont pas été explicitement marquées nonvirtual (ou final ou private), comme c'est le cas en java ?

                                              Juste pour la remarque, il y a de l'inling en Java, il est fait à chaud (c'est une optimisation du compilateur JIT) sous certaines conditions.

                                              Juste pour faire remarqué que ce n'est pas complètement interdit :)

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

                                              • [^] # Re: Donc pour résumer…

                                                Posté par . Évalué à 2.

                                                D'ailleurs c'est le cas de la plupart des setters/getters, non ?

                                                • [^] # Re: Donc pour résumer…

                                                  Posté par . Évalué à 2.

                                                  Oui c'est entre autre lié à la taille du byte code

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

                                              • [^] # Re: Donc pour résumer…

                                                Posté par . Évalué à 2.

                                                L'inlining arrive beaucoup plus souvent qu'on ne le croit. Si une classe n'a pas de descendantes (beaucoup de classes sont dans ce cas) alors toutes les méthodes qu'elle définit peuvent être inlinées. C'est la CHA (Class Hierarchy Analysis) qui regarde quelles classes sont chargées au runtime. Cela permet beaucoup d'optimisations spéculatives.

                                  • [^] # Re: Donc pour résumer…

                                    Posté par . Évalué à 1.

                                    Il y a des gens qui "#include" des trucs autres que des .h?.

                                    La plupart des développeurs C++.
                                    Les fichiers de la SL n'ont pas d'extensions. Pas mal de libs utilisent ".hpp". On voit régulièrement ".tpp" pour les templates. Entres autres.
                                    Mais honnêtement, ça ne me gênerait pas outre mesure que l'on ne soit plus obligé de se taper les header guards, bien au contraire.

                                    Y compris dans la justification de la non-dépréciation de syntaxes obsolètes qui n'ont aucune justification (goto, void*, etc)

                                    Hum. Goto, parce que tu le remplaces par les exceptions, c'est ça? Le support des exceptions augmente, selon un document que j'ai la flemme de rechercher, le binaire d'environ 10%. Pas forcément possible pour tout le monde. void*, ça nous permets de nous interfacer avec du C, avec des fonctions aussi «obsolètes» que dlopen.

                                    Autant je te rejoins sur les header guards, autant pas sur les goto/void* (alors que je n'utilise pas goto et que je ne suis que client de void*).

                                    Il y a du code de 40 ans qui compile grâce à ces trucs? Qu'est-ce qu'on s'en fout

                                    C'est un point de vue. Pas celui de tout le monde.

                                • [^] # Re: Donc pour résumer…

                                  Posté par . Évalué à 2.

                                  Là où tous les autres langages ajoutent des fonctionnalités sereinement dès qu'il en est question en C++, on voit un « non, mais attends tu ne te rend pas compte de comment c'est compliqué… ».

                                  Peu de langages ont un modèle de compilation séparé (puisqu’on parle des inclusions).

                                  Je crois que c’était une bonne idée en 1970, quand C a été créé, puisque ça permettait de compiler des programmes beaucoup plus gros que la mémoire dont on disposait à l’époque. Aujourd’hui, C++ doit vivre avec cet héritage (remettre en cause ce modèle n’est pas à l’ordre du jour, ça casse trop de chose). Mais il y a des évolutions, les propositions sur les modules vont dans ce sens.

                                  Du coup, oui, d’autres langages font autrement, plus simplement, mais c’est aussi parce qu’ils ont des contraintes différentes :). Quand on parle du poids de l’historique, oui, c’est clair que C++ a un lourd passé.

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

                                  • [^] # Re: Donc pour résumer…

                                    Posté par . Évalué à 3.

                                    Après pour me citer moi-même, c'est pas un problème insurmontable : https://linuxfr.org/users/rewind/journaux/c-17-est-sur-les-rails#comment-1648265.

                                    Je crois que c’était une bonne idée en 1970, quand C a été créé, puisque ça permettait de compiler des programmes beaucoup plus gros que la mémoire dont on disposait à l’époque.

                                    Aujourd'hui (et depuis longtemps puisque Java le fait), les compilateurs prennent directement l'ensemble des sources en entrée. Mais même aujourd'hui, pour le C++ qui fait faire un travail infernal au compilateur, je ne suis pas sûr que ça ne consomme pas des quantité folles de mémoire…

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

                            • [^] # Re: Donc pour résumer…

                              Posté par . Évalué à 4.

                              Ce qu'on peut reprocher au C++, c'est de ne même pas définir la manière correcte de faire les choses

                              Java à interdit aux dév. de faire des fonctions. Résultat, ils font des classes statiques. Toutes les exceptions doivent être rattrapées? Ok, mais on ne fais rien.

                              Je parle de Java ici, mais c'est juste le 1er exemple qui me viens en tête. Quand un langage limite ses utilisateurs, ils contournent les limitations, parfois à bon escient (la classe statique, avec le main statique) parfois non (les exceptions) et dans tous les cas, je trouve que ça alourdit la lecture.
                              C++ n'est pas parfait niveau simplicité de lecture (surtout quand on lit du code template) mais j'apprécie qu'il ne me force pas à suivre une école de pensée unique, qui ne sera pas optimale pour certains cas particuliers.

                      • [^] # Re: Donc pour résumer…

                        Posté par . Évalué à 2.

                        L’usage d’une fonction clone est problématique aussi (qu’est censé faire Derived::clone(Base const& b) si on lui donne un Base& qui est de type Derived2 ? Un assert ? pas terrible…).

                        Je n'utiliserais pas la même signature moi, je ferai plutôt static bool Derived::clone( Base const& b, Derived& a ); du coup, tu fais ce que tu veux quand ça échoue, avec a qui n'est pas modifié si tu as fais les choses bien.

                        Personnellement, j'opte de plus en plus souvent pour l'usage de constructeurs nommés (static foo::make( ... );) et quand ça rate (paramètre foireux, initialisation impossible, whatever), selon mon API je throw ou retourne foo().
                        C'est un idiome plutôt pratique, qui peut palier à ton problème (si je l'ai bien compris) et même permettre d'avoir plusieurs «constructeurs» avec les mêmes signatures, exemple (bidon) si tu veux générer un pixel RGB, tu peux avoir 3 constructeurs nommés pour faire les nuances d'une couleur unique.
                        En bonus, dans certains cas, ça évite de se taper les operator= & Ctors, alors que l'on veux juste ajouter un moyen de convertir un type dans un autre. En malus, je "perds" une partie des conversions automatiques (enfin, surtout parce que j'ai souvent la flemme de me tapper les operator=&Ctors).

    • [^] # Re: Donc pour résumer…

      Posté par . Évalué à 3.

      Tout à fait d'accord. J'étais fan de C++, et petit à petit j'en ai vu les problèmes, les limitations, la complexité. Puis j'ai touché au C#, au rust, par exemple. Maintenant, je ne veux plus faire de C++.

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

      • [^] # Re: Donc pour résumer…

        Posté par . Évalué à 2.

        Plus facile, plus séduisant est le côté obscur.
        Tu as juste succombé.
        Peut-être peux-tu encore être sauvé. Écoute ton cœur. Et ta raison. Et reviens du côté du bien.

  • # ...

    Posté par . Évalué à 7.

    Finalement, en lisant tous ces commentaires, on se rend compte que C++, c'est ceux qui en mangent le moins qui en parlent le plus. Les mauvaises diront sans doute que les autres sont en train de coder en C++ parce que ça prend plus de temps que dans d'autres langages.

    • [^] # Re: ...

      Posté par (page perso) . Évalué à 10.

      Pourtant si tu fais du C++ t'as bien le temps de poster un commentaire à chaque fois que tu recompiles

      ---> []

    • [^] # Re: ...

      Posté par . Évalué à 0.

      Je me réponds à moi-même pour signaler que j'ai oublié le mot «langue» dans la dernière phrase : «les mauvaises langues diront…»

    • [^] # Re: ...

      Posté par . Évalué à 3.

      Je ne vois vraiment pas sur quoi tu te bases pour dire ça. Plusieurs personnes ont fait des commentaires sur la complexité du C++ et disent pourtant travailler avec tous les jours.

      • [^] # Re: ...

        Posté par . Évalué à 1.

        Plusieurs personnes ont fait des commentaires sur la complexité du C++ et disent pourtant travailler avec tous les jours.

        Oui, mais plusieurs personnes ont fait des commentaires sans vraiment savoir de quoi elles parlaient. Juste des on-dit, des trucs qui datent de plus de 10 ans, etc. C'est saoulant, vraiment. Et surtout, c'est systématique sur les dépêches ou les journaux traitant de C++. Tu pourras refaire l'historique, mais quasiment à chaque fois, il y a quelqu'un pour venir nous parler de Rust. J'ai envie dire : faites des dépêches sur Rust pour parler de Rust, et arrêtez de venir pour pomper l'air pour nous expliquer que C++ c'est de la merde. Chacun est quand même libre d'utiliser le langage qu'il veut. Moi je vis très bien avec C++, ça me va et ce n'est pas les pseudos-arguments des détracteurs qui vont me convaincre. Oui C++ a des défauts, quel langage n'en a pas ? Si le langage parfait existait, ça se saurait depuis longtemps. Et ces défauts, ils me vont bien, je considère même que certains sont des avantages ! Bref, le dénigrement systématique de C++, surtout par des gens qui ne le pratiquent pas, ça me gonfle.

        • [^] # Re: ...

          Posté par . Évalué à 4.

          arrêtez de venir pour pomper l'air pour nous expliquer que C++ c'est de la merde.

          Si tu penses au thread auquel je pense, il partait de quelqu'un qui demandait quels étaient nos reproches envers le C++ ; forcément, les bons aspects n'apparaîtront pas.

          Pour reprendre tes mots, arrête de venir nous pomper l'air et fait un thread pour mettre en avant ce que nous aimons dans le C++.

          des trucs qui datent de plus de 10 ans

          En même temps, quand la pérennité est un des arguments pour le C++ et que nombre de librairies se traînent des trucs vieux de plus de 10 ans (tousse Qt tousse), ça va forcément revenir dans les discussions.

          • [^] # Re: ...

            Posté par . Évalué à 1.

            Pour reprendre tes mots, arrête de venir nous pomper l'air et fait un thread pour mettre en avant ce que nous aimons dans le C++.

            Je ne suis pas un VRP de C++, je n'oblige personne à croire que c'est le langage parfait. Je fais des journaux de temps en temps pour ceux que ça intéresse et basta.

        • [^] # Re: ...

          Posté par . Évalué à 9.

          C'est dommage de poster à un endroit où les commentaires sont ouverts, si tu ne veux pas de commentaires :)

          Le C++ est un excellent langage, il est performant et très flexible. Sur la dernière dépêche, il y a eu un petit jeu pour reproduire une syntaxe aussi jolie qu'en ruby qui était intéressante.

          Passer son temps à s'extasier sur quelque chose (« oh comment il est trop beau mon langage ») est un peu dommage. Les critiques sont une bonne occasion de présenter des aspects peut être moins connu du langage.

          Regarde les dépêches qui parlent de gtk ou gnome où il se font pourrir depuis pas mal de temps.

          Et surtout, c'est systématique sur les dépêches ou les journaux traitant de C++.

          Effectivement comme celui-ci par exemple ? Gestion de l'erreur - C++ - std::optional ou celui-là Switch, chaîne constante et c++… Ah oui tu dois parler de la précédente dépêche ! Les coulisses du standard C++.

          Hum, elle serait pas un peu imaginaire ta vendetta ? On a beaucoup de contenu autour du C++ et ils sont tous bien noté. La plupart du temps, ils sont très bien accueilli (désolé, mais tu réclame d'en faire l'historique). Le comparer à Rust est assez logique puisqu'il se pose en C++-killer, même si ça peu peut être fatiguer.

          Bref le fait que les commentaires sur cette dépêche aient un peu dérapé, ne doit pas être l'arbre qui cache la foret. C'est dommage de nous présenter tes talents de dramaturge pour si peu.

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

          • [^] # Re: ...

            Posté par . Évalué à 0.

            Le comparer à Rust est assez logique puisqu'il se pose en C++-killer, même si ça peu peut être fatiguer.

            Voilà, tout est dit, merci.

            • [^] # Re: ...

              Posté par . Évalué à 5.

              C'est à dire ? Il n'est pas sur le même segment ? Il n'est pas une alternative direct ?

              « killer » ça te fais peur pour ton langage favori ? C'est juste une expression, hein ? Les Samsung Galaxy Sn se posent en iphone-killer ça n'empêche personne d'acheter des téléphones sans prise jack.

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

        • [^] # Re: ...

          Posté par . Évalué à -1.

          à chaque fois, il y a quelqu'un pour venir nous parler de Rust. J'ai envie dire : faites des dépêches sur Rust pour parler de Rust, et arrêtez de venir pour pomper l'air pour nous expliquer que C++ c'est de la merde

          J'ai plussé parce que ce que tu dis est vrai, mais perso je mets un point d'honneur à aller troller dans toutes les dépêches sur Rust pour dire que quand ça compilera sur autant de plateforme que C++ on pourra se mettre à discuter, donc je pourris les dépêche Rust plutôt que d'écrire des dépêche C++.

    • [^] # Re: ...

      Posté par . Évalué à 2. Dernière modification le 04/10/16 à 21:22.

      Finalement, en lisant tous ces commentaires, on se rend compte que C++, c'est ceux qui en mangent le moins qui en parlent le plus.

      Alors plutôt que de s'adresser aux critiques, tu fais de l'attaque personnelle.

      C'est pas comme ça que tu vas taire les critiques.

      Perso je m'en fous de C++, j'utilise C# et Ruby…

      Par contre j'aimerais beaucoup qu'il y ait une vraie alternative, et j'espère que Rust va s'imposer.

      Aussi "aucun langage n'est parfait". Certes. Ce n'est pas pour autant que les autres langages mélangent autant les genres de programmation, les syntaxes, et autre… Tu dévies les critiques avec des platitudes qui n'ont aucun intérêt.

      Enfin quoi, il faut bien se rendre compte que C++ est un cas unique en son genre, et c'est pour ça qu'il est critiqué (à tort ou à raison).

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

  • # Jai: Language for game programmming

    Posté par . Évalué à 1.

    Je ne saurais que vous suggérer le vlog de Jonathan Blow (real du jeu Braid) qui est parti sur la création d'un langage - Jai - en partant de zéro. Son premier argumentaire et le second. Il aime profondément le C++..

    J'ai clairement des lacunes en compilateur, en théorie des langages de programmation parce que c'est entre autre une chose qui m'intéresse peu mais j'aime malgré tout aller en profondeur de temps en temps même si j'arriverais assez vite à un point bloquant.

    Et puis pour revenir au C++, les on-dits et ce genre de dépêche ont un effet assez dissuasifs sur moi. Je suis plutôt à l'aise en C, très à l'aise en Python et en Java mais n'ai plus le courage ou patience d'aller dans ce vaste univers quitte à me priver d'excellentes bibliothèques, le C++, restera une blackbox pour moi.

    Désolé de diverger un poil, en tous cas super depêche et fil de commentaires (en général).

    @+

    ps: Si DevNewton passait par là, je serais intéressé d'avoir son opinion.

    • [^] # Re: Jai: Language for game programmming

      Posté par . Évalué à 7.

      Et puis pour revenir au C++, les on-dits et ce genre de dépêche ont un effet assez dissuasifs sur moi. Je suis plutôt à l'aise en C

      Je vais en profiter pour parler de ce que j'apprécie dans le C++, ça changera du thread ou les défauts sont listés :)

      Pour moi, les principaux avantages du C++ sur le C (qui à été mon 1er «vrai» langage), c'est qu'il permets d'automatiser plus le code source, sans forcément avoir recours à des trucs capilo-tractés. Certes, on peut faire des trucs complexes, mais tout ne l'est pas.
      Typiquement, les 3 (5 en fait, si on compte le const_cast et le cast old school qui est toujours valide) casts de C++ ont un gros avantage sur celui du C, ils permettent d'éviter pas mal de bugs.

      J'ajouterais en 2ème position les template.
      Pas la STL (la STL, c'est un concentré de C++: classes, templates, exceptions), juste les templates (la SL possède de beaux exemples comme std::sort déjà mentionné) qui permettent de quasi-supprimer les macros, tout en permettant un code plus lisible et plus sûr (les types sont vérifiés à la compilation). Malheureusement, je ne crois pas (mais je n'ai pas vérifié) qu'il soit aussi simple de manipuler des chaînes de caractères de la même manière en template qu'en macros, donc ces derniers ont toujours leur utilité ( ## et #, même si c'est illisible, m'on déjà été utiles ).
      Par exemple, j'ai un template qui permets de caster d'un type vers un autre, et si il y a dépassement (enfin, j'ai pas fait une couverture de test à 100% et j'ai testé que sur des pointeurs et des entiers), ça fait assert ratée. En release, ça ne fait que dalle. Accessoirement, il est plus court à taper que static_cast ;)
      J'aurai sûrement pu coder ça à coup de macros, mais je pense que j'aurai dépassé la 60aine de lignes (et j'aime bien aérer en hauteur).

      En 3ème position, les fonctions à l'intérieur des structures, plus clairement: les constructeurs et destructeurs: fini de devoir se rappeler d'appeler SDL_FreeFooBar(ptr);.
      Contrairement à l'idée reçue, pas besoin d'exceptions ni d'héritage pour en tirer bon profit. Dans le cas de l'héritage, je dirais même que c'est de toute façon un truc que j'évite, comme probablement pas mal de monde et ce, dans tous les langages.
      En standard, on a std::unique_ptr, mais, ça ne gère que les pointeurs (et ce n'est pas le seul reproche que j'ai envers unique_ptr, mais comme je l'ai dis, je ne considère pas la STL comme le principal point fort de C++, au risque de me faire descendre :)): pas les resources indexées (handles de fichier, de ressources OpenGL, whatever). Du coup, j'ai une classe qui gère l'unicité des ressources, et qui sait que -1, c'est l'équivalent de nullptr. Enfin, en réalité, elle ne le sait pas: je lui ai collé un paramètre template pour indiquer quelle valeur est équivalent d'une ressource vide.

      4ème: la SL. C'est à dire la bibliothèque standard sans les conteneurs. Je ne dis pas qu'ils sont inutiles, mais ils ont les «inconvénients»:

      • de lancers des exceptions (et on peut pas faire autrement) dans différents cas.
      • de ne pas permettre de contrôler facilement l'usage mémoire (sauf std::array et, dans une moindre mesure, std::vector. std::map/set et leurs versions multi sont souvent implémentées en RBtree, pas sûr pour les unordered, et de toute façon le standard n'impose rien me semble).

      En revanche, la SL, c'est des algo plus ou moins classiques et complexes à faire qu'on peut utiliser d'une ligne. Enfin, surtout depuis c++11, parce qu'avant les lambda et std::begin()/std::end() c'était un peu pénible :)

      Le reste, pour moi, ce sont des plus, mais pas les killer features du C++ (surtout du point de vue d'un dev C, j'imagine):

      Quand t'as la flemme de faire tes propres conteneurs, tu peux utiliser la STL.
      Et si t'as envie de faire tes conteneurs (genre un ring buffer ou un dictionnaire dont on peut connaître l'usage mémoire), mais que tu ne veux pas réécrire les algorithmes standards, tu as juste à implémenter un itérateur: tu bénéficies toujours des algo de la SL.
      Bien sûr, le récent fait de pouvoir utiliser les algo sur des tableaux C-style est bien pratique, ainsi que le range for (peut-être qu'il existe aussi en C11? Je ne sais pas du tout ce qu'ils ont ajouté.).
      Et pour finir, les exceptions et les flux: beaucoup les aimes, d'autres non, mais dans certains tout ça est bien utile.

      En conclusion: tu devrais essayer. Juste, dans un premier temps, remplacer gcc par g++ (ou clang par clang++ ou… bref) et les casts. C'est plus verbeux c'est vrai, mais ça réduit les problèmes.
      Puis avec les templates et la RAII, tu te retrouves déjà avec des codes bien plus réduits que leurs versions C, sans être obligé de libérer explicitement les instances de BDD (chose qu'il faut faire manuellement tant en C qu'en Java si on se méfie du côté aléatoire du GC) et sans forcément avoir un code plus dur à lire, même s'il est vrai que plus on combine de techniques, plus le code deviens délicat à manipuler. D'où l'intérêt d'intégrer les avantages au fur et à mesure (et avec le temps de considérer certains morceaux comme peu utiles en regard des services rendus).

      • [^] # Re: Jai: Language for game programmming

        Posté par . Évalué à 2.

        sans être obligé de libérer explicitement les instances de BDD (chose qu'il faut faire manuellement tant en C qu'en Java si on se méfie du côté aléatoire du GC)

        Oui et non, depuis Java 7 tu as le try-with-resources qui est (avec une syntaxe un peu étrange) du RAII:

        https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

        (mais bon loin de moi l'envie de défendre Java juste de moquer le fait que ce langage a introduit le RAII 25 ans après C++, en même temps que les classes abstraites avec des méthodes non nulles, comme il avait introduit les templates 20 après C++, j'attends bien sûr l'héritage multiple pour dans 10 ans, encore que les lambdas étant arrivés l'an passé on peut rêver)

        • [^] # Re: Jai: Language for game programmming

          Posté par . Évalué à 2.

          Puisque tu troll… La différence entre le RAII du C++ et celui qu'on voit en Java (ou en python), c'est que dans ces derniers il n'y a pas moyen d'avoir d'ub. Ce qui peut arriver quand tu as des problèmes dans tes destructeurs.

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

          • [^] # Re: Jai: Language for game programmming

            Posté par . Évalué à 4.

            Ouais mais là il troll léger : on peut mieux faire, surtout sur sa dernière remarque avec les lambda, j'en ris encore ! :-D

            Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ? Elles sont où les applications partielles, la curryfication, les fonctions comme citoyens de premières classes ?

            Reprenons l'histoire brève, incomplète et globalement erronée des langages de programmations :

            1936 - Alan Turing invents every programming language that will ever be but is shanghaied by British Intelligence to be 007 before he can patent them.

            1936 - Alonzo Church also invents every language that will ever be but does it better. His lambda calculus is ignored because it is insufficiently C-like. This criticism occurs in spite of the fact that C has not yet been invented.

            1970 - Guy Steele and Gerald Sussman create Scheme. Their work leads to a series of "Lambda the Ultimate" papers culminating in "Lambda the Ultimate Kitchen Utensil." This paper becomes the basis for a long running, but ultimately unsuccessful run of late night infomercials. Lambdas are relegated to relative obscurity until Java makes them popular by not having them.

            1973 - Robin Milner creates ML, a language based on the M&M type theory. ML begets SML which has a formally specified semantics. When asked for a formal semantics of the formal semantics Milner's head explodes. Other well known languages in the ML family include OCaml, F#, and Visual Basic.

            1983 - Bjarne Stroustrup bolts everything he's ever heard of onto C to create C++. The resulting language is so complex that programs must be sent to the future to be compiled by the Skynet artificial intelligence. Build times suffer. Skynet's motives for performing the service remain unclear but spokespeople from the future say "there is nothing to be concerned about, baby," in an Austrian accented monotones. There is some speculation that Skynet is nothing more than a pretentious buffer overrun.

            1990 - A committee formed by Simon Peyton-Jones, Paul Hudak, Philip Wadler, Ashton Kutcher, and People for the Ethical Treatment of Animals creates Haskell, a pure, non-strict, functional language. Haskell gets some resistance due to the complexity of using monads to control side effects. Wadler tries to appease critics by explaining that "a monad is a monoid in the category of endofunctors, what's the problem?"

            1996 - James Gosling invents Java. Java is a relatively verbose, garbage collected, class based, statically typed, single dispatch, object oriented language with single implementation inheritance and multiple interface inheritance. Sun loudly heralds Java's novelty.

            Là on commence à avoir du bon troll sur les querelles de chapelles entre langages. :-P

            Et pour être honnête, Turing a développé son concept de machine pour trancher une polémique entre Gödel et Church sur la notion de fonctions calculables, et de son propre aveu il reconnaissait que la notation de Church était préférable à son propre concept pour traiter la notion.

            Ensuite quand il s'est agit de typer le lambda-calcul, les théoriciens sont tombés sur la correspondance de Curry-Howard ou correspondance preuve-programme : un programme n'est que la preuve d'un théorème mathématique (voir la dernière page de l'article, par exemple). On pourra, également, regarder le code de ma fonction-lemme reroot qui mime un raisonnement par récurrence : on initialise avec les cas Arr puis pour le cas général on commence par utiliser l'hypothèse de récurrence sur pa'. ;-)

            À l'arrivée, on se retrouve avec un langage qui date de 1936, un système de type (pour Haskell ou OCaml) qui est peu ou proue la logique propositionnelle du second ordre (i.e. la logique des stoïciens qui a plus de 2000 ans), et une analogie forte avec la plus ancienne de toutes les sciences : la mathématique. Pour ce qui est du recul et des possibilités d'un tel système, on peut difficilement tenter la comparaison.

            Mais comme disait Platon : « nul ne peut entrer ici s'il n'est géomètre » (à moins que ce ne soit « s'il neigeait au mètre »); et son maître Socrate, dans La République, comparait l'humanité à un peuple d'esclaves passant leur temps à observer des ombres changeantes sur le mur d'une caverne, incapables qu'ils étaient de se libérer de leurs chaînes pour se retourner et contempler le monde immuable des Idées dont les ombres n'étaient qu'une pâle projection. :-)

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

            • [^] # Re: Jai: Language for game programmming

              Posté par . Évalué à 2. Dernière modification le 10/10/16 à 23:52.

              Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ? Elles sont où les applications partielles, la curryfication, les fonctions comme citoyens de premières classes ?

              En fait dans les langages de programmation de ces dernières décennies le mot "lambda" correspond à une fonction anonyme, c'est loin d'être une spécificité de C++.
              https://en.wikipedia.org/wiki/Anonymous_function

              Et oui l'expression vient bien du lambda calcul.

              Et du coup les fonctions comme citoyens de première zones sont un cas plus général que les seuls opérateurs lambdas.
              Parce qu'une fonction comme citoyen de première classe c'est pas forcément un lambda, ça peut aussi être une méthode de classe ou une fonction C.

              Les trucs vraiment intéressant sont là, car en effet les lambdas c'est juste* une facilité d'écriture:
              http://en.cppreference.com/w/cpp/utility/functional/function
              http://en.cppreference.com/w/cpp/utility/functional/bind
              voire tout
              http://en.cppreference.com/w/cpp/utility/functional

              *: bon mais c'est bien pratique quand même hein :-)

              • [^] # Re: Jai: Language for game programmming

                Posté par . Évalué à 2.

                En fait dans les langages de programmation de ces dernières décennies le mot "lambda" correspond à une fonction anonyme, c'est loin d'être une spécificité de C++.

                C'est gentil de me l'apprendre… mais je le savais déjà, hein. ;-)

                Mon message répondait à barmic dans une volonté trollesque clairement affichée, et donc empreinte de mauvaise foi. Cela étant je restais dans le sujet du commentaire initial de ce fil puisque Le Gab y parlait de théorie des langages, et que mon commentaire aborde le sujet à plus d'un titre.

                Je suis allé voir tes liens, et la syntaxe me semble bien lourde et difficile à appréhender pour un non initié. Pour comparer :

                let tab = Array.init 5 (fun i -> 2 * i);;
                val tab : int array = [|0; 2; 4; 6; 8|]
                
                Array.iteri (fun i v -> Printf.printf "%i -> %i\n" i v) tab;;
                0 -> 0
                1 -> 2
                2 -> 4                                                                          
                3 -> 6
                4 -> 8
                - : unit = ()
                
                (* ou mieux avec les applications partielles, merci Curry ! *)
                let tab = Array.init 5 (( * ) 2);;
                val tab : int array = [|0; 2; 4; 6; 8|]
                
                Array.iteri (Printf.printf "%i -> %i\n") tab;;
                0 -> 0
                1 -> 2
                2 -> 4                                                                          
                3 -> 6
                4 -> 8
                - : unit = ()

                Et quand on parle de fonctions comme citoyens de premier classe, on entend aussi par là qu'elles peuvent être utilisées dans un contexte polymorphe comme n'importe quel autre terme sans syntaxe particulière.

                let tab = Array.init 5 (fun i -> ( * ) i);;
                val tab : (int -> int) array = [|<fun>; <fun>; <fun>; <fun>; <fun>|]
                
                Array.iteri (fun i f ->  Printf.printf "%i * 2 -> %i\n" i (f 2)) tab;;
                0 * 2 -> 0
                1 * 2 -> 2
                2 * 2 -> 4
                3 * 2 -> 6
                4 * 2 -> 8
                - : unit = ()

                Mais je peux continuer le troll aussi : si tu veux essayer de me convaincre que la POO (oui ça ne touche pas que le C++ ;-) n'est pas une totale aberration logique et intellectuelle, il va falloir te lever de bonne heure. L'esprit humain est structurellement constitué selon des schèmes fonctionnels, et la POO est fondée sur une étrange analyse logique de la classification et du rapport de subordination des concepts en genres et espèces : à comparer avec les type classes de Haskell, par exemple, et la génération automatique de code dont Guillaum a donné des exemples dans ce commentaire.

                Que l'entendement humain soit fonctionnel par essence, ce n'est pas une découverte qui date d'aujourd'hui : je pourrais te renvoyer à la théorie du schématisme dans la Critique de la Raison Pure de Kant qui date de… 1781. ;-)

                Et au cas où la référence à un tel ouvrage, dans une question qui traite d'informatique et de théorie des langages, t'étonnerait, je m'auto-cite à partir d'un commentaire sur un autre journal :

                Dans son ouvrage Les Métamorphoses du calcul, le mathématicien et informaticien Gilles Dowek revient sur l'évolution du calcul de l'antiquité jusqu'à nos jours et ce qui a donné naissance à l'informatique et l'ordinateur. Un des moments de cette histoire est une polémique lorsque Gottlob Frege voulut s'attaquer à la thèse centrale de l'ouvrage de Kant sus-cité, à savoir que tous les jugements mathématiques sont synthétiques a priori. Il a échoué, et pour cause, Kant avait raison et Gödel le prouva via son théorème d'incomplétude (ce qui équivaut au problème de l'arrêt en informatique). M. Dowek le reconnaît dans son livre, mais en appendice il cite malheureusement le cogito cartésien comme exemple de jugement synthétique a priori : c'est une erreur, ce jugement est analytique et non synthétique.

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

                • [^] # Re: Jai: Language for game programmming

                  Posté par . Évalué à 3.

                  Je suis allé voir tes liens,
                  (…)
                  Et quand on parle de fonctions comme citoyens de premier classe, on entend aussi par là qu'elles peuvent être utilisées dans un contexte polymorphe comme n'importe quel autre terme sans syntaxe particulière.

                  Bah t'as pas dû tout lire alors.
                  Voici comme définir une variable qui est une fonction:

                  auto ajouter = [](int x, int y) { return x+y; };
                  auto uneautre = &MaClasse:unemethode;
                  Et comment on capture une variable en passant une fonction en paramètre:

                  int total;
                  macollection.iterer([&total](int n) { total += n; });
                  Tu peux trouver la syntaxe difficile si tu veux, mais perso je trouve que séparer les paramètres capturés (crochets) des paramètres d'appel (parenthèses) clarifie beaucoup de choses (notamment en précisant si la capture est pas référence ou par copie).

                  Mais je peux continuer le troll aussi : si tu veux essayer de me convaincre que la POO (oui ça ne touche pas que le C++ ;-) n'est pas une totale aberration logique et intellectuelle, il va falloir te lever de bonne heure

                  Je n'essayerai pas de te convaincre. Mais en fait selon le problème que tu cherches à résoudre ce n'est pas toujours le même modèle de programmation (objet, fonctionnel, procédural) le plus efficace et avoir plusieurs outils ne nuit pas.

                  D'ailleurs un des avantages de C++ est que tu peux faire un peu tout avec. Je ne dis pas qu'il faut tout faire avec juste que tu n'es pas obligé de faire du fonctionnel (contrairement à lisp/scheme qui te l'impose), que tu n'es pas obligé de faire de l'objet avec (contrairement à java qui te l'impose au moins dans la forme et les bibliothèques/frameworks existants), etc.

                  • [^] # Re: Jai: Language for game programmming

                    Posté par . Évalué à 1.

                    J'ai l'impression que l'on ne se comprend pas, ou que tu ne sais pas ce qu'est la programmation fonctionnelle.

                    Premièrement, pour la curryfication et les applications partielles, un langage fonctionnel digne de ce nom (donc basé sur le lambda-calcul) n'a pas besoin d'artifice particulier à la std::bind pour faire cela.

                    (* le principe de la curryfication se voit dans le type inféré :
                     * c'est une fonction qui prend un int et renvoie une fonction
                     * qui prend un int pour retourner un int *)
                    let plus i j = i + j;;
                    val plus : int -> int -> int = <fun>
                    
                    (* donc si je lui donne un int, j'obtiens une fonction des
                     * int vers les int *)
                    let plus_2 = plus 2;;
                    val plus_2 : int -> int = <fun>
                    
                    plus_2 3;;
                    - : int = 5
                    
                    (* la curryfication, ça se voit aussi comme cela : *)
                    let plus = fun i -> fun j -> i + j;;
                    val plus : int -> int -> int = <fun>
                    
                    let plus_2 = plus 2;;
                    val plus_2 : int -> int = <fun>
                    
                    plus_2 3;;
                    - : int = 5

                    Ensuite ton exemple avec macollection.iterer, je ne vois pas où il illustre le statut de citoyen de première classe pour les fonctions. De ce que je comprends, iterer est une méthode d'un objet itérable qui prend une fonction en paramètre : mais cela fait partie du type même de la méthode d'attendre un tel paramètre. De ce que je comprends, c'est équivalent à ça :

                    let (+=) x i = x := !x + i;;
                    val ( += ) : int ref -> int -> unit = <fun>
                    
                    let sum a = fun x -> Array.iter ((+=) x) a;;
                    val sum : int array -> int ref -> unit = <fun>
                    
                    let x = ref 3 in
                    sum [|1; 2; 3|] x; !x;;
                    - : int = 9

                    Moi ce que je disais c'est que le type des tableaux 'a array est polymorphe et contient des éléments de n'importe quel type 'a qui peut être un type de fonction, comme dans l'exemple :

                    let tab = Array.init 5 (fun i -> fun x -> x += i);;
                    val tab : (int ref -> unit) array = [|<fun>; <fun>; <fun>; <fun>; <fun>|]

                    Ici le type 'a est instancié à la valeur particulière int ref -> unit qui est celui des fonctions qui appliquent des effets de bords sur les pointeurs sur int. On peut en user ainsi :

                    let a = Array.init 5 (fun i -> ref 1);;
                    val a : int ref array =
                      [|{contents = 1}; {contents = 1}; {contents = 1}; {contents = 1};{contents = 1}|]
                    
                    Array.iteri (fun i f -> f a.(i)) tab;;
                    - : unit = ()
                    
                    a;;
                    - : int ref array = 
                      [|{contents = 1}; {contents = 2}; {contents = 3}; {contents = 4};{contents = 5}|]

                    Mais en fait selon le problème que tu cherches à résoudre ce n'est pas toujours le même modèle de programmation (objet, fonctionnel, procédural) le plus efficace et avoir plusieurs outils ne nuit pas.

                    Je n'ai jamais dit le contraire, d'ailleurs OCaml permet d'utiliser les paradigmes impératif, orienté objet et fonctionnel (mais vraiment du fonctionnel, pas l'ersatz que tu as illustré jusqu'à maintenant).

                    En attendant, quand je ne trolles pas pour répondre à du troll, je fournis des réponses plus pertinentes et adaptées aux autres commentaires. Comme quand dans cette dépêche, freem se demandait à quoi pouvait servir les shared_ptr et que je lui ai proposé de s'en servir pour implémenter des tableaux immuables. D'ailleurs, j'attends toujours des adeptes du C++ qu'ils m'éclairent sur la pertinence de mon exemple.

                    P.S: Bon, le lambda-calcul ça date de 1936, et on a du attendre C++11 pour en avoir un ersatz à lire tes exemples. Pour l'inférence de type et l'argorithme d'unification de Robinson qui en est la base, ça date de 1965. Il va falloir attendre combien de temps, pour celui-là ? L'algorithme en question est présenté sur le blog de la Société Informatique de France par Gilles Dowek.

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

                    • [^] # Re: Jai: Language for game programmming

                      Posté par . Évalué à 0.

                      un langage fonctionnel digne de ce nom (donc basé sur le lambda-calcul)

                      Ensuite ton exemple avec macollection.iterer, je ne vois pas où il illustre le statut de citoyen de première classe pour les fonctions.

                      Moi ce que je disais c'est que le type des tableaux 'a array est polymorphe et contient des éléments de n'importe quel type 'a qui peut être un type de fonction

                      J'en déduis donc que selon toi un langage fonctionnel digne de ce nom doit être faiblement typé. Ou au moins doit avoir un type racine qui peut contenir n'importe quoi. C++ ne sera donc jamais un langage fonctionnel digne de ce nom.

                      En attendant, quand je ne trolles pas pour répondre à du troll, je fournis des réponses plus pertinentes et adaptées aux autres commentaires.

                      Bravo. Tu es donc quelqu'un de bien. Je suppose que c'était le message.

                      Et du coup grâce à ta constructivité(1) sur certains sujets tu a acheté le droit de faire preuve de mauvaise foi en écrivant, ceci, comme si cette définition du lambda était propre à C++, y compris OCaml (le seul langage digne de ce nom):

                      Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                      Pour ensuite répondre:

                      En fait dans les langages de programmation de ces dernières décennies le mot "lambda" correspond à une fonction anonyme, c'est loin d'être une spécificité de C++.

                      C'est gentil de me l'apprendre… mais je le savais déjà, hein. ;-)

                      Après je considère le droit à troller (et donc à troller de mauvaise foi) comme une liberté fondamentale qui devrait être inscrite dans la déclaration des droits de l'homme.

                      J'ai juste bien rigolé en lisant cette médaille que tu porte fièrement à la poitrine:

                      En attendant, quand je ne trolles pas pour répondre à du troll, je fournis des réponses plus pertinentes et adaptées aux autres commentaires.

                      (accompagné de deux liens où tu réinventes l'eau chaude en découvrant manifestement ce qu'est un smart pointer, je dis ça pour donner de la profondeur à l'adjectif "pertinentes")

                      • [^] # Re: Jai: Language for game programmming

                        Posté par . Évalué à 4.

                        J'en déduis donc que selon toi un langage fonctionnel digne de ce nom doit être faiblement typé. Ou au moins doit avoir un type racine qui peut contenir n'importe quoi. C++ ne sera donc jamais un langage fonctionnel digne de ce nom.

                        Je pense que tu as tort. D'une part, il existe un lambda-calcul typé, dont se servent les langages fonctionnels (et LISP, par exemple, est fortement typé, mais le typage est dynamique). D'autre part, je ne vois pas ce qui te fait dire que l'exemple donné par Kantien est faiblement typé. Simplement l'application de l'opération (+=) sera adaptée au type contenu dans un élément spécifique du tableau, mais le typage est statique et fort en OCaml.

                      • [^] # Re: Jai: Language for game programmming

                        Posté par . Évalué à 4.

                        J'en déduis donc que selon toi un langage fonctionnel digne de ce nom doit être faiblement typé

                        Gné ??? Haskell ou OCaml sont fortement et statiquement typé. Voici la page des publications de Jacques Garrigue qui est le responsable principal du système de types du langage OCaml.

                        Par contre, je n'ai toujours pas vu comment tu illustrais le principe de la curryfication en C++ ni celui des fonctions comme citoyen de première classe. Au passage, Curry qui a donné le nom à ce principe était un mathématicien et logicien qui se prénommait… Haskell. ;-)

                        Bravo. Tu es donc quelqu'un de bien. Je suppose que c'était le message.

                        Que nenni ! Je pointais juste du doigt qu'un utilisateur de C++ (ce que je ne suis pas) posait une question sur l'utilité des shared_ptr et qu'aucun spécialiste de ce langage n'avait dénié lui répondre. C'est un peu dans le même ordre que ce commentaire de Benoît Sibaud :

                        Déjà six commentaires pour dire à quel point c'est un mauvais choix. Et pas un capable d'expliquer et/ou de proposer une solution ou d'être constructif ou donner des références. Pauvre communauté.

                        Pour ce passage, je ne comprends pas trop :

                        Et du coup grâce à ta constructivité(1) sur certains sujets tu a acheté le droit de faire preuve de mauvaise foi en écrivant, ceci, comme si cette définition du lambda était propre à C++, y compris OCaml (le seul langage digne de ce nom):

                        Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                        Pour ensuite répondre:

                        En fait dans les langages de programmation de ces dernières décennies le mot "lambda" correspond à une fonction anonyme, c'est loin d'être une spécificité de C++.
                        C'est gentil de me l'apprendre… mais je le savais déjà, hein. ;-)

                        Premièrement, je n'ai jamais, mais alors jamais, affirmé que OCaml était le seul langage digne de ce nom. Je dis juste qu'un langage qui ne fournit pas nativementt les concepts et principes de la programmation fonctionelle (dont l'archétype est le lambda-calcul) ne mérite pas d'être qualifié de langage fonctionnel. J'allais dire que j'avais passé l'âge de jouer à qui a la plus grosse en comparant les langages, mais à dire vrai je ne l'ai jamais eu : c'est ridicule, je n'en ai jamais vu l'intérêt.

                        Deuxièmement, oui il y a avait du troll et de la mauvaise foi dans mon message initial : mais c'était clairement affiché. ;-) Cela étant sur le site C++ référence à Lambda, on peut bien lire :

                        Lambda functions (since C++11)
                        Constructs a closure: an unnamed function object capable of capturing variables in scope.

                        Le concept est donc bien apparu avec le C++11 et représente des fonctions anonymes qui capturent des variables d'environnement en construisant une fermeture. Comme cette notion de fermeture est apparue avec le langage scheme en 1975, et que tu raillais Java qui avait mis plus de temps à les implémenter, je me suis permis de railler également le C++. Sur la page wikipédia sur les fermetures, tu pourras en particulier y lire :

                        En Haskell, grâce à la curryfication, toute fonction peut générer des fermetures lorsqu'on lui passe seulement une partie de ses arguments
                        [..]
                        En OCaml […] grâce à la curryfication, toute fonction peut générer une fermeture lorsqu'on lui passe seulement une partie de ses arguments.

                        c'est ce que j'ai fait avec mon exemple de plus, en faisant une application partielle :

                        let plus i j = i + j
                        
                        let plus_2 = plus 2

                        Ici, on génère une fermeture en capturant 2 dans l'environnement d'évaluation de plus_2. Mais on peut aussi le faire anonymement :

                        fun i -> plus 2 i

                        Cela étant, je n'ai toujours pas d'exemples de curryfication et de leur usage pour générer des fermetures en C++.

                        Au passage, cette notion est empruntée à la logique formelle et au calcul des prédicats : c'est la notion de variable libre et de variable liée (et date donc de la fin du XIXème siècle). Dans un énoncé comme celui-ci : Pour tout entier n, n + i = n, la variable n est liée par le quantificateur universel (on peut changer son alias par j sans changer le sens de l'énoncé) tandis que la variable i est libre (elle représente un entier indeterminé). Pour pouvoir interpréter un tel énoncé, qui est en fait paramétré par un entier indéterminé i, il faut l'évaluer dans un environnement qui attribue une valeur à cette entier i : voilà d'où vient ce concept de fermeture. En l'occurrence pour cet énoncé, il ne sera vrai que pour la valeur i = 0 si on l'interprète dans son environnement naturel qui est celui de l'ensemble des entiers naturels. C'est là la base de la théorie des modèles qui n'est pas sans intérêt pour l'étude de la sémantique des langages de programmation.

                        accompagné de deux liens où tu réinventes l'eau chaude en découvrant manifestement ce qu'est un smart pointer, je dis ça pour donner de la profondeur à l'adjectif "pertinentes".

                        Je vois que tu es prompte à répondre, mais que tu n'as rien compris au code que j'ai écrit. Il ne s'agissait pas de réinventer l'eau chaude ou de découvrir ce qu'était un smart pointer mais :

                        • de montrer quel était l'équivalent de cette notion dans le langage OCaml;
                        • et d'en faire usage pour implémenter des tableaux persistants.

                        C'est la partie graissée qui est l'objectif principal du code : donner un exemple d'utilisation possible de ce concept sous la forme des shared_ptr (qui peut aussi existait sous la forme des unique_ptr), car telle était la question de freem. Le premier point était simplement là pour que le lecteur non familier avec les concepts de OCaml, mais familier avec ceux du C++, puisse comprendre le fonctionnement du code et traduise en C++.

                        Pour reprendre ma question trollesque du premier message :

                        ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                        Dis : tu t'y connais un peu en programmation fonctionnelle et en théorie des langages ou tu veux juste troller ?

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

                        • [^] # Re: Jai: Language for game programmming

                          Posté par . Évalué à 0.

                          Ton problème, c’est que soit tu trolles de mauvaise foi, soit tu ne sais pas lire:

                          Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                          Il n’est pas marqué ça sur le site. Il est marqué que les lambdas sont apparues dans le langage C++ depuis la version C++11. C’est pour ça que je dis que tu es de mauvaise foi, ou que tu ne sais pas lire.

                          Il existait déjà bien auparavant boost::function.

                          ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                          Si tu connaissais un peu le C++, tu verrais qu’il y a beaucoup de choses qui relèvent de la programmation fonctionnelle dès qu’on fait de la métaprogrammation. Il y a même une syntaxe presque aussi horrible que celle d’Haskell :) (dans un style différent, certes).

                          Mais plus généralement, tu juges de la compétences des concepteurs d’un langage (ne me dis pas que tu poses seulement une question, la tournure est loin d’être innocente et tu le sais très bien) que tu ne connais pas sur une mauvaise lecture que tu fais (probablement volontairement) de documentations sur un site web qui n’est pas en lien avec le comité en charge de l’évolution du langage.  Il y a des limites à ce qui acceptable comme troll :).

                          À part ça, pour shared_ptr, j’ai déjà répondu (avec des exemples). C’est un partage de responsabilité de la durée de vie. Ton idée de liste chaînée ou de tableaux persistants est (de ce que j’en ai compris) un peu à côté de la plaque de ce point de vue.

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

                          • [^] # Re: Jai: Language for game programmming

                            Posté par . Évalué à 7. Dernière modification le 11/10/16 à 18:28.

                            Ton problème, c’est que soit tu trolles de mauvaise foi, […]
                            En même temps, par définition, un troll de mauvaise foi. Ceci étant dit, je suis d'accord avec ce que tu dis sur le côté troll de Kantien (mais comme il disait, il trolle ouvertement, du coup je lui pardonne à moitié :-)). Cependant :

                            Si tu connaissais un peu le C++, tu verrais qu’il y a beaucoup de choses qui relèvent de la programmation fonctionnelle dès qu’on fait de la métaprogrammation. Il y a même une syntaxe presque aussi horrible que celle d’Haskell :) (dans un style différent, certes).

                            Le langage de templates (qui est Turing complet, d'ailleurs), est un langage fonctionnel (affectation unique, etc.). C++ lui-même, en tant que langage n'est pas fonctionnel, mais offre quelques mécanismes permettant d'écrire des programmes dans un style fonctionnel. C'est quand même différent d'un langage fonctionnel par nature.

                            Lorsque je dis que C++ est un langage orienté objet, c'est parce qu'il offre des mécanismes qui me permettent directement d'exprimer un programme selon les concepts de la POO : niveau d'encapsulation (privé, protégé, public, etc.), héritage, groupement des données et des messages (méthodes/fonctions) qui leur sont associés, etc.

                            De même, lorsque je dis qu'un langage est fonctionnel, je m'attends à ce que les fonctions soient des objets de première classe, mais aussi que je puisse naturellement compter sur une expression de mes programmes en termes de valeurs (« immutable ») et pas de variables ( « mutables », et qui offrent donc des effets de bord), d'être capable d'exprimer des fonctions imbriquées dans d'autres fonctions si j'en ai le besoin, etc. La notion de récursivité (du code, des types, etc.) est inhérente au paradigme fonctionnel. Ce n'est pas le cas en C++. Oui, on peut jouer avec const, boost::function (et maintenant les lambdas), et même avec de la méta-programmation pour contraindre le système et forcer un style fonctionnel, mais ça ne fait pas de C++ un langage fonctionnel. De même que ce n'est pas parce qu'on peut programmer dans un style objet en C que ça en fait un langage orienté objet.

                            • [^] # Re: Jai: Language for game programmming

                              Posté par . Évalué à 3.

                              Lorsque je dis que C++ est un langage orienté objet

                              Tu commets une première erreur :).

                              ça ne fait pas de C++ un langage fonctionnel.

                              Ce n’est pas non plus ce que j’ai dit. Je répondais simplement à

                              ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                              En faisant remarquer qu’un pan entier du langage suivait ces paradigmes…

                              Néanmoins, C++ est multiparadigme (et donc, C++ n’est pas un langage « orienté objet », il est un langage intégrant parmi d’autres le paradigme objet), et de plus en plus, les paradigmes fonctionnels trouvent leur place dans le langage. Je suis très surpris d’ailleurs que les tenants des langages fonctionnels y trouvent matière à critiquer, en fait, ils devraient au contraire s’en réjouir :).

                              Les langages fonctionnels purs n’existent de toute façon que pour faire de la preuve mathématique : dès que tu travailles sur un système, tu introduis à minima les problématiques d’aliasing et de mutabilité (même si ton langage l’interdit au niveau variable, tu peux l’avoir au niveau système de fichier par exemple, des ressources systèmes, de la console, etc.), ce qui casse toute la « pureté » de ton langage. C’est d’ailleurs je pense une des raisons de l’échec (relatif, bien entendu) des langages fonctionnels : leur niveau d’abstraction ne correspond finalement pas tant que ça à la réalité à laquelle est confronté le programmeur.

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

                              • [^] # Re: Jai: Language for game programmming

                                Posté par . Évalué à 5. Dernière modification le 11/10/16 à 22:21.

                                Néanmoins, C++ est multiparadigme (et donc, C++ n’est pas un langage « orienté objet », il est un langage intégrant parmi d’autres le paradigme objet),

                                Bon, je me suis demandé s'il fallait que je pinaille, JUSTEMENT à cause de ça. :) J'ai raccourci, mais si tu veux, de même que OCaml est connu pour être un langage fonctionnel, il est lui aussi multi-paradigme. C'est juste que le paradigme impératif et objet me semble quand même plus secondaire quand on discute de ce langage. Même chose pour C++ : oui, il permet la prog générique, procédurale, objet, etc. Mais superficiellement, C++ est quand même largement associé à la programmation objet.

                                EDIT: Tiens d'ailleurs, lorsque je dis que C++ est un langage orienté objet, je ne dis pas qu'il n'est que cela, ET TOC.

                                (j'aime pas avoir tort, ça se voit ?)

                          • [^] # Re: Jai: Language for game programmming

                            Posté par . Évalué à 2.

                            Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture : ils ne savent pas ce qu'est le lambda-calcul les concepteurs et développeurs C++ ?

                            Il n’est pas marqué ça sur le site. Il est marqué que les lambdas sont apparues dans le langage C++ depuis la version C++11. C’est pour ça que je dis que tu es de mauvaise foi, ou que tu ne sais pas lire.

                            Euh, c'est exactement ce que j'ai écrit ! :-O Enfin, j'ai sous entendu que l'apparition était dans le langage C++; je ne voulais pas entendre par là qu'ils prétendait que C++11 avait inventé les lambda. Je le reconnais, cela pouvait prêter à confusion (enfin, si l'on ne fait pas beaucoup d'effort). ;)

                            Qu'il y ait de la mauvaise foi dans mes propos précédents, c'est évident, je ne l'ai jamais nié, j'ai même annoncé la couleur. Par contre, je ne sais pas lequel de nous deux à des problèmes de compréhension sur ce qu'il lit.

                            Mais plus généralement, tu juges de la compétences des concepteurs d’un langage (ne me dis pas que tu poses seulement une question, la tournure est loin d’être innocente et tu le sais très bien) que tu ne connais pas sur une mauvaise lecture que tu fais (probablement volontairement) de documentations sur un site web qui n’est pas en lien avec le comité en charge de l’évolution du langage. Il y a des limites à ce qui acceptable comme troll :).

                            C'était clairement de la mauvaise foi en ce qui concerne les concepteurs du langage, je sais très bien qu'ils sont loin d'ignorer ce qu'est le lambda-calcul. Pour ce qui est des développeurs C++, cela dépend : DerekSagan ou toi ne semblent pas y connaître grand chose. ;-)

                            C’est un partage de responsabilité de la durée de vie. Ton idée de liste chaînée ou de tableaux persistants est (de ce que j’en ai compris) un peu à côté de la plaque de ce point de vue.

                            Alors tu n'as pas compris mon code, mais c'est peut être mon explication qui est défectueuse. En OCaml, en dehors des valeurs de type int et des constructeurs constants comme Nil on ne manipule que des pointeurs qui se comportent comme des smart pointer. Tous mes tableaux se partagent la responsabilité de la durée de vie du 'a array qui sert de mémoire partagée. Quand on a des types somme comme Cons of ... ce sont des smart pointer nommés en lecture seule, avec les ref ce sont des smart pointer en lecture-écriture. Les types somme cela sert à poser des étiquettes sur les branches du graphe des pointeurs, et ça s'implémente en C aussi : le runtime et le GC de OCaml sont codés en C.

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

                        • [^] # Re: Jai: Language for game programmming

                          Posté par . Évalué à -2.

                          Gné

                          Un beau concentré de ta pensée. Le reste est habillage pour donner de l'apparence à l'absence de contenu.

                      • [^] # Re: Jai: Language for game programmming

                        Posté par . Évalué à 4.

                        Moi ce que je disais c'est que le type des tableaux 'a array est polymorphe et contient des éléments de n'importe quel type 'a qui peut être un type de fonction

                        J'en déduis donc que selon toi un langage fonctionnel digne de ce nom doit être faiblement typé. Ou au moins doit avoir un type racine qui peut contenir n'importe quoi. C++ ne sera donc jamais un langage fonctionnel digne de ce nom.

                        L'inférence de type vérifie que le typage est correct. Quand tu touche à OCaml tu te rend vite compte qu'il a un typage fort.
                        L'inférence de type (je vais essayer de l'expliquer mais je vais me casser les dents, on va me reprendre derrière ^), c'est le compilateur qui va tenter de déduire les types non pas avec du transtypage et/ou de la conversion (PHP c'est toi qu'on regarde !), mais en propageant les types partout où c'est nécessaire.

                        Si tu as une fonction qui peut retourner soit le type A soit le type B, tu doit faire attention à gérer chacun des 2 types partout ou utiliser des fonctions ou des opérateurs qui peuvent prendre ces 2 types en paramètre. C'est en ça qu'ils sont polymorphiques. Le polymorphisme dans le paradigme fonctionnel ne s'appuie pas sur une hiérarchie de classe, mais sur le fait de pouvoir prendre un type parmi d'autres. C'est ce qu'ils appellent les union type si je ne m'abuse (mais je crois que je m'abuse, c'est un autre nom).

                        En tout cas ça permet de définir un type comme étant soit d'un type soit d'un autre (c'est pas limité à 2). Ça se rapproche du type union du C sauf que ça ne cache pas le type au compilateur, il va propager la liste des types partout où c'est nécessaire.

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

                        • [^] # Re: Jai: Language for game programmming

                          Posté par . Évalué à 0.

                          Si tu as une fonction qui peut retourner soit le type A soit le type B, tu doit faire attention à gérer chacun des 2 types partout ou utiliser des fonctions ou des opérateurs qui peuvent prendre ces 2 types en paramètre. C'est en ça qu'ils sont polymorphiques. Le polymorphisme dans le paradigme fonctionnel ne s'appuie pas sur une hiérarchie de classe, mais sur le fait de pouvoir prendre un type parmi d'autres. C'est ce qu'ils appellent les union type si je ne m'abuse (mais je crois que je m'abuse, c'est un autre nom).

                          Si je comprends bien, c’est un l’équivalent d’un variant, mais implicite ? Le polymorphisme dans les langages fonctionnels repose sur le duck typing (c’est le cas en python, et aussi en go si je ne me plante pas, ce n’est pas lié au paradigme fonctionnel), mais ce que tu dis c’est que là le compilateur s’autorise à créer de nouveaux types ?

                          Il me semble qu’ocaml fait du multiple dispatch (j’en suis sûr pour Haskell) en fonction du type réel des arguments. Est-ce à dire que si tu as une fonction f qui peut renvoyer soit un A, soit un B, et deux surcharges de g, l’une pour A, l’autre pour B, cela va fonctionner correctement de chaîner les appels g(f("toto")) ? Ce serait élégant.

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

                          • [^] # Re: Jai: Language for game programmming

                            Posté par . Évalué à 3.

                            le duck typing (c’est le cas en python, et aussi en go si je ne me plante pas

                            Le duck typing c'est un typage fort mais dynamique. Go fais ça de manière statique et on appelle ça un typage structurel.

                            Ce sont des typages où pour le premier on vérifie dynamiquement si les noms de méthodes correspondent. Imagine en python hors classe new style, tu peux ajouter manuellement toutes les méthodes nécessaires à n'importe quelle variables pour t'en servir comme d'un tableau par exemple, c'est le principe du dynamique et on ne peut donc pas trop se contenter de regarder le type d'un objet pour savoir si on peut ou non s'en servir comme ça.

                            En go c'est un peu différent, une variable peu correspondre à une interface qu'elle ne connaît pas (lors de la création de l'objet, on a pas indiqué qu'il descendait de cette interface), mais si c'est le cas il peut être utilisé comme un objet de cette interface. Le compilateur vérifie que l'objet a bien une structure qui correspond à l'interface demandé.

                            Dans le paradigme fonctionnel, une variable a un type qui lui est propre et qui reste inchangé de sa création à sa libération. Ce qu'il y a c'est que tu peut indiquer que tu es capable de manipuler plusieurs types différents (soit parce que tu le spécialise via du pattern matching, soit parce que tu te contente de faire des courses que tu peux faire avec les 2).

                            ce que tu dis c’est que là le compilateur s’autorise à créer de nouveaux types ?

                            Non non c'est toi qui les crée.

                            Est-ce à dire que si tu as une fonction f qui peut renvoyer soit un A, soit un B, et deux surcharges de g, l’une pour A, l’autre pour B, cela va fonctionner correctement de chaîner les appels g(f("toto")) ? Ce serait élégant.

                            Oui et ça peut même aller plus loin, tu peux spécialiser pour des valeurs donné (par exemple avoir une fonction pour un entier et une pour la valeur 0, c'est souvent comme ça que sont géré les cas terminaux des fonctions récursives). Soit ça se fait par création de multiples fonctions soit via le pattern matching (je crois que ocaml n'a pas de multimethode - on ne parle pas de surcharge en fonctionnel car les fonctions cohabitent-). En tout cas oui le typage est propagé à travers les méthodes (c'est là que réside la beauté du bousin).

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

                        • [^] # Re: Jai: Language for game programmming

                          Posté par . Évalué à 2.

                          Pour la version longue : voir ce commentaire que j'avais fait l'an dernier sur les principes de base du lambda-calcul typé. J'y comparais le typage de OCaml (qui est décidable, sauf sur quelques corner cases, d'où l'inférence de types) et celui de Coq qui lui ne l'est pas et donc le programmeur doit le dériver lui même explicitement, ce qui s'appelle faire une démonstration au sens mathématique du terme.

                          Pour la version courte : disons que le language des types c'est, grosso modo, celui de la logique propositionnelle du second ordre. C'est à dire que l'on a des variables propositionnelles : A, B, C..., l'implication logique A -> B (si A alors B), la conjonction A et B et la disjonction exclusive A ou B. L'implication : c'est le type des fonctions de A dans B; la conjonction : c'est les types produits, l'équivalent des struct du C; la disjonction : c'est les types sommes, les union du C mais en mieux.

                          On appelle ces deux genres de types, types sommes et types produits à cause de cette relation : A et (B ou C) <-> (A et B) ou (A et C). Ce qui donne une des définitions du concept d'algèbre de Boole au sens où l'entendent les mathématiciens : c'est un corps dans lequel tout élément est sont propre carré ((A et A) <-> A). L'algèbre a deux éléments {vrai, faux} étant la plus simple et la plus triviale de telles algèbres.

                          Par exemple, le type des listes : type 'a liste = Nil | Cons of 'a liste exprime une disjonction : c'est soit la constante propositionnelle Nil, soit une variable propositionnelle indexée par le type lui-même (ce qui fait que le type exprime une disjonction infinie, disjonction qui pour un type 'a donné est isomorphe à l'ensemble des entiers naturels 0 ou 1 ou 2 ou 3...). Pour les types produits, on peut prendre par exemple les points du plan : type point = {x:float; y:float}; c'est la donnée d'un int et d'un int, d'où la conjonction.

                          Ce qui fait qu'à l'arrivée, on tombe bien sur la logique des stoïciens.

                          Le principe d'application des fonctions, par exemple, c'est la régle de déduction logique dite du modus ponens : Si A alors B, or A, donc B. Règle que l'on écrit en logique propositionnelle : (A -> B) -> A -> B. À comparer avec le type inféré de la fonction suivante :

                          let apply f x = f x;;
                          val apply : ('a -> 'b) -> 'a -> 'b = <fun>

                          L'inférence de type relève de la preuve automatique, Coq c'est de la preuve assistée par ordinateur, et les principes du typage du lambda-calcul proviennent tous de la théorie de la démonstration. Et donc coder en fonctionnel, c'est faire une preuve : le principe même de la correspondance de Curry1-Howard, ou correspondance preuve-programme. Le compilateur gueule, au niveau du typage, quand il considère qu'il y a un vice de forme dans la preuve.


                          1. le même Curry qui a donné son nom à la curryfication, et qui s'appelait Haskell Brooks Curry. ;-) 

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

                        • [^] # Re: Jai: Language for game programmming

                          Posté par . Évalué à -4. Dernière modification le 13/10/16 à 09:42.

                          Mouaih enfin OCaml est un exemple de langage à typage faible, mine de rien. Même un exemple souvent cité.

                          https://fr.wikipedia.org/wiki/Typage_fort

                          • [^] # Re: Jai: Language for game programmming

                            Posté par . Évalué à 7.

                            Cette page raconte n'importe quoi… Si tu veux à la place de typage fort. Parlons de sûreté de type. OCaml est typé statiquement donc le compilateur vérifie le typage avant l'exécution et il n'est pas autorisé à faire des conversions ou des transtypages. Donc OCaml a un typage sûr. Il ne faut pas se laisser berner par l'inférence de type. Quand tu utilise le auto en C++, tu as de l'inférence de type, ça n'est pas du type erasure et ben là c'est pareil sauf que c'est plus poussé. De la même manière pour le type Somme qui décrit une variable comme l'un de ses types. Ça t'oblige à gérer chaque type.

                            Quand tu commence à écrire de l'OCaml le système de type c'est le premier truc qui vient te foutre une baffe :)

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

                          • [^] # Re: Jai: Language for game programmming

                            Posté par . Évalué à 9.

                            Apparemment tu n'interprètes pas correctement le paragraphe:

                            Un langage est fortement typé si les conversions implicites de types sont formellement interdites. Si de telles conversions sont possibles, le langage est faiblement typé. Exemples répondant à ce critère : OCaml, Haskell.

                            Il faut dire qu'il n'est pas mise en page de la façon la plus claire possible, mais pour rendre la chose plus claire tu peux garder la même sémantique en enlevant la phrase du mileu:

                            Un langage est fortement typé si les conversions implicites de types sont formellement interdites. Exemples répondant à ce critère : OCaml, Haskell.

                            Et là, il est tout de suite plus claire qu'OCaml et Haskell sont cités comme des langages à typage fort.

                            • [^] # Re: Jai: Language for game programmming

                              Posté par . Évalué à 2.

                              Exemple de conversion de type qui doit être explicite sous peine de voir le type checker te foutre une baffe — pour reprendre l'expression de barmic.

                              (* l'opérateur infixe +. exige deux paramètres de types float *)
                              (+.);;
                              - : float -> float -> float = <fun>
                              
                              (* si on lui donne un int, il proteste ! *)
                              1 +. 2.3;;
                              Error: This expression has type int but an expression was expected of type float
                              
                              (* il faut faire une conversion explicite *)
                              float_of_int 1 +. 2.3;;
                              - : float = 3.3

                              Ce qui ne semble pas être le cas du C++, si j'en crois ce commentaire de Guillaum (développeur C++ expérimenté) :

                              Aller, j'en ajoute (C++ est mon outil de travail 8h par jour depuis plus de 10 ans) en me focalisant sur les problèmes qui m’embêtent vraiment au quotidien et pour lesquels il n'y a pas de solution simple. J'essaye de rester objectif ;)

                              • Les casts implicites. Plus particulièrement le fait que les flottants, les ints, les bools, les enums, et j'en passe, c'est la même chose. Le downcast implicite d'une classe vers une autre. Je sais que c'est la philosophie du langage. Combien de fois je me suis fais manger par un int négatif convertit en unsigned…

                              […]

                              C'est moi qui graisse. :-)

                              Et je le comprends : faire de la conversion de type sans l'accord explicite du développeur, ça ne devrait pas être permis ! C'est quoi ces compilateurs qui prennent des décisions qui affectent la sémantique du code dans le dos du programmeur ?

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

                              • [^] # Re: Jai: Language for game programmming

                                Posté par (page perso) . Évalué à 8.

                                Yahoo, je suis enfin reconnu par mes pairs ;)

                                En effet, en C++ tous les types numériques sont implicitement castables en un autre type numérique, et comme les enums et les bools sont des types numériques, cela devient vite comique. Tu peux cependant activer des warnings quand ces conversions font de la perte d'information, mais c'est simplement un warning et surtout il dépend du compilateur :

                                void add(float, int, int){}
                                
                                int main()
                                {
                                    if(3.5)
                                    {
                                        add(2, false, 3.5);
                                    }
                                }

                                La compilation avec clang nous donne :

                                λ skyskolem ~ → clang++ test_conv.cpp -Wall -Wextra -Wall -Wconversion
                                test_conv.cpp:5:5: warning: implicit conversion from 'double' to 'bool' changes value from 3.5 to true
                                      [-Wliteral-conversion]
                                        if(3.5)
                                        ~~ ^~~
                                test_conv.cpp:7:17: warning: implicit conversion from 'double' to 'int' changes value from 3.5 to 3
                                      [-Wliteral-conversion]
                                                add(2, false, 3.5);
                                                ~~~           ^~~
                                2 warnings generated.
                                

                                (note: j'avoue que je n'ai pas cherché si il existe un ensemble de flags pour en trouver plus).

                                À cela j'ajoute un piège dans lequel beaucoup de développeurs C++ tombent facilement c'est que par définition en C++ un constructeur à un argument est un opérateur de cast implicite :

                                struct Test
                                {
                                    Test();
                                    Test(int);
                                    Test(bool);
                                };
                                
                                void foo(Test, Test, Test);
                                
                                int main()
                                {
                                    Test a;
                                    foo(5, true, a);
                                }

                                Ce problème se règle en préfixant les constructeur du mot clé explicit, mais l'oubli est facile.

                                Pour finir ce message, je voudrais expliquer comment cela fonctionne dans Haskell. C'est la même situation qu'en OCaml au sens que les conversions implicites n'existent pas. Cependant en ce qui concerne les literaux numériques c'est un peu particulier, ceux-ci sont polymorphiques, ce qui veut dire que 2 est potentiellement un Float/Double/Int/Integer/Int32/Word16 et cela ne sera décidé que par le biais de l’inférence de type, en gros c'est la fonction qui prendra notre literal en paramètre qui décidera de son sort.

                                Notons cependant que ce choix est fait en fonction du literal. En gros, 3 peut être un Double, mais 3.2 ne peut pas être un Int, exemples, avec deux fonctions:

                                f :: Int -> Int
                                f x = 2 * x
                                
                                g :: Float -> Float
                                g x = 2 * x
                                Prelude> g 2
                                4.0
                                Prelude> f 2
                                4
                                Prelude> g 2.3
                                4.6
                                Prelude> f 2.3
                                
                                <interactive>:20:3: error:
                                     No instance for (Fractional Int) arising from the literal 2.3
                                     In the first argument of f, namely 2.3
                                      In the expression: f 2.3
                                      In an equation for it: it = f 2.3

                                C'est un beau compromis dans le sens où il n'est pas nécessaire de typer explicitement ses literaux, mais aucun cast implicite n'est effectué. En contrepartie il faut de temps à autre être explicite sur le type car l'inférence de type s'y perd.

                                C'est aussi pratique quand on commence à implémenter ses propres types numériques pour différents cas de figure (calcul vectoriel, auto differentiation, …) et où il est agréable de profiter de l'écriture de litéreaux. Exemple non complet, soit une fonction f :

                                f v = 2 * (v * v)

                                Le type inferé par Haskell est f :: Num a => a -> a. En gros, une fonction qui prend un Num et retourne un Num.

                                Cela fonctionne pour les littéraux "classiques" :

                                Prelude> f 2
                                8
                                Prelude> f 2.3
                                10.579999999999998

                                Mais j'ai aussi définis mon type numérique maison Expr Float qui représente un arbre d'expression et je peux utiliser f dessus :

                                > (f 5) :: Expr Float
                                Mul (Constant 2.0) (Mul (Constant 5.0) (Constant 5.0))

                                Bon, je me suis encore lâché et j'ai expliqué pleins de trucs au lieu de regarder le meilleur pâtissier ;) Bonne soirée.

                                • [^] # Re: Jai: Language for game programmming

                                  Posté par . Évalué à 5. Dernière modification le 14/10/16 à 11:23.

                                  Pour les cas que tu décris en Haskell, c'est ce qui manque à l'heure actuelle en OCaml : le polymorphisme ad hoc (en), qu'il faut distinguer du polymorphisme paramétrique (en). Le second est le polymorphisme que l'on trouve en OCaml : une fonction peut prendre en entrée un type quelconque sans que cela change son code dans sa forme (l'algorithme est identique) comme c'est le cas avec des fonctions sur les 'a list ou 'a array, par exemple.

                                  Le polymorphisme ad hoc, lui, est ce qui permet entre autre de faire de la surcharge d'opérateur : une multiplicité de code partage le même alias, et le code adapté sera choisi en fonction du type des entrées. C'est par exemple pour cela qu'en Haskell il y a un seul opérateur d'addition + alors qu'en OCaml il y en un pour chaque type de données : + pour les int et +. pour les float. Ce sont les type classes qui permettent cela en Haskell, dispositif qui ne possède pas encore d'équivalent en OCaml mais il y a des personnes qui y travaillent.

                                  Leo White, Frédéric Bour et Jeremy Yallop ont proposé les modular implicts qui s'insprirent entre autre des type classes de Haskell et des implicits de Scala. Dans leur article, ils comparent leur approche avec celles faites dans d'autres langages, comme les concepts pour le C++, section qui renvoie à un article collaboratif dont un des auteurs est Bjarne Stroustrup : Concepts: linguistic support for generic programming in C++. Comme je ne suis pas abonné à l'ACM, je n'ai pas pu lire l'article, et je me demande si ce sont bien les concept que les développeurs C++ attendent et dont parle la dépêche.

                                  L'idée de base pour ajouter cette possibilité dans le langage OCaml est de passer par le système des modules du langage et des fonctions sur les modules ou foncteurs. Une approches lourde et qui crée beaucoup d'indirection à base de pointeurs, dans l'exemple de l'addition, est de passer par un type somme :

                                  type num = I of int | F of float
                                  
                                  let add i j = match i, j with
                                    |I i, I j -> I (i + j)
                                    |I i, F j -> F (float_of_int i +. j)
                                    |F i, I j -> F (i +. float_of_int j)
                                    |F i, F j -> F (i +. j)
                                  
                                  add (I 1) (F 2.3);;
                                  - : num = F 3.3

                                  C'est extrêmement lourd dans l'écriture à l'usage. Leur idée est donc de passer par le système de modules et les modules de première classe. On commence par définir une signature pour les objets numériques :

                                  module type Num = sig
                                    type t
                                    val add: t -> t -> t
                                    val mul: t -> t -> t
                                  end

                                  Puis, on définit deux fonctions1add et mul qui prennent, en plus de leurs deux paramètres habituels, un module qui choisira l'implémentation adaptée.

                                  let add (type a) (module M:Num with type t = a) (i:a) (j:a) = M.add i j;;
                                  val add : (module Num with type t = 'a) -> 'a -> 'a -> 'a = <fun>
                                  
                                  let mul (type a) (module M:Num with type t = a) (i:a) (j:a) = M.mul i j;;
                                  val mul : (module Num with type t = 'a) -> 'a -> 'a -> 'a = <fun>

                                  Il reste à implémenter deux modules de la bonne signature pour nos deux types de base int et float.

                                  module Int_num : (Num with type t = int) = struct
                                    type t = int
                                    let add = (+)
                                    let mul = ( * )
                                  end;;
                                  module Int_num :
                                    sig type t = int val add : t -> t -> t val mul : t -> t -> t end
                                  
                                  module Float_num : (Num with type t = float) = struct
                                    type t = float
                                    let add = (+.)
                                    let mul = ( *. )
                                  end;;
                                  module Float_num :
                                    sig type t = int val add : t -> t -> t val mul : t -> t -> t end

                                  Pour l'usage cela se passe ainsi :

                                  add (module Int_num) 1 2;;
                                  - : int = 3
                                  
                                  add (module Float_num) 1.3 2.5;;
                                  - : float = 3.8

                                  Leur proposition consiste à ajouter quelques modifications légères au langage de base de telle façon que le module en argument soit implicite et inféré automatiquement par le compilateur en fonction du type des autres paramètres. Ce qui permettrait d'avoir la syntaxe (qui n'est pas acceptée à l'heure actuelle) suivante :

                                  (* ajout du mot clé `implicit` en tête de la définition du module *)
                                  implicit module Int_num : Num = struct
                                    type t = int
                                    let add = (+)
                                    let mul = ( * )
                                  end
                                  
                                  implicit module Float_num : Num = struct
                                    type t = float
                                    let add = (+.)
                                    let mul = ( *. )
                                  end
                                  
                                  (* le paramètre implicite est mis entre accolades {}
                                   * et non entre parenthèse () *)
                                  let add {M : Num} (i : M.t) (j : M.t) = M.add i j
                                  
                                  add 1 2;;
                                  - : int = 3
                                  
                                  add 1.3 2.5;;
                                  - : float = 3.8

                                  Le compilateur se chargeant de regarder, dans le dictionnaire des modules implicites ayant la bonne signature, de trouver celui dont le type t correspond à celui des paramètres donnés à la fonction.


                                  1. en réalité ce n'est pas tout à fait ce choix qui est fait pour l'implémententation des fonctions add et mul, mais c'est pour simplifier, et ils justifient leur approche à la section 2.4 qui permet d'atteindre un plus haut niveau de polymorphisme. 

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

                                  • [^] # Re: Jai: Language for game programmming

                                    Posté par (page perso) . Évalué à 3.

                                    J'adore tes réponses ! Complètes, avec des liens, super. Déjà que je trouve que les réponses que j'écris sont souvent longue à écrire, tu dois y passer un temps non négligeable, merci.

                                    Cependant j'ai maintenant une question. Tu dis qu'en OCaml, il faut changer d'opérateur entre les float et les int. Mais qu'en est-t-il entre les différentes représentations de ceux-ci. Genre si tu as des int16, des int32, des int64, des int de taille infinie ? C'est chaque fois un autre opérateur où il y a une solution intermédiaire ?

                                    • [^] # Re: Jai: Language for game programmming

                                      Posté par . Évalué à 1. Dernière modification le 14/10/16 à 16:42.

                                      Il n'y a pas de quoi pour mes réponses, on est là pour échanger et partager. Quand j'ai du temps, j'essaye d'être assez précis, d'illustrer le plus que je peux et de fournir des références si le lecteur veut prolonger sa réflexion de son côté.

                                      Pour les différentes représentations, elles ont chacune leur module avec leurs opérateurs propres :

                                      • module Int32 pour les entiers 32-bits signés;
                                      • module Int64 pour les entiers 64-bits signés;
                                      • module Num pour les opérations sur des rationnels en précision arbitraire.
                                      (* exemple sur les Int32 *)
                                      Int32.add 32l 64l;;
                                      - : int32 = 96l
                                      
                                      (* mais on peut le renommer localement + si on préfère *)
                                      let (+) = Int32.add in 32l + 54l;;
                                      - : int32 = 86l
                                      
                                      (* idem avec les Int64 *)
                                      Int64.add 32L 544L;;
                                      - : int64 = 576L
                                      
                                      let (+) = Int64.add in 32L + 544L;;
                                      - : int64 = 576L
                                      
                                      (* mais si on travaille dans un module où l'on ne manipule
                                       * que des Int32, il suffit de l'ouvrir et de renommer *)
                                      open Int32;;
                                      
                                      let (+) = add;;
                                      val ( + ) : int32 -> int32 -> int32 = <fun>
                                      
                                      127l + 233l;;
                                      - : int32 = 360l
                                      
                                      (* là comme j'étais dans la REPL j'ai perdu le + sur les int
                                       * il me suffit de le renommer et c'est good ! :-) *)
                                      let (+) = Pervasives.(+);;
                                      val ( + ) : int -> int -> int = <fun>
                                      
                                      21 + 21;;
                                      - : int = 42

                                      Une fois qu'il y aura le polymorphisme ad hoc, on pourra avoir un opérateur + « unique » pour les différentes représentations. J'ai mis les guillemets car en réalité, cela correspond à un seul alias pour différents codes mais qui préserve tout de même l'essentiel : le typage fort du langage.

                                      En attendant, on peut néanmoins déjà utiliser les principes du code avec les modules de première classe (et oui les modules aussi sont des citoyens de premières classe : c'est du lambda-calcul après tout ;-) mais avec paramètre explicite. Ils sont abordés au chapitre First-Class Modules (en) du livre Real World OCaml, et sont illustrés pour coder un query-handling framework.

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

                                    • [^] # Re: Jai: Language for game programmming

                                      Posté par . Évalué à 0.

                                      Petite précision pour illustrer qu'une fois le système des modular implicits définitivement mis au point et stabilisé, cela ne demandera pas de grands changements dans la base de code existante.

                                      Pour rappel, on peut déjà écrire :

                                      module type Num = sig
                                        type t
                                        val add: t -> t -> t
                                        val mul: t -> t -> t
                                      end;;
                                      module type Num = sig type t val add : t -> t -> t val mul : t -> t -> t end
                                      
                                      let add (type a) (module M:Num with type t = a) i j = M.add i j;;
                                      val add : (module Num with type t = 'a) -> 'a -> 'a -> 'a = <fun>

                                      Avec les modules de première classe, on a déjà un polymorphisme élargi mais le module doit être donné explicitement. Les modules Int32 et Int64 correspondent à cette interface, même s'ils définissent plus de choses : il n'est pas nécessaire de se restreindre aux seuls éléments d'une interface pour la satisfaire, il suffit qu'ils y soient. D'où :

                                      add (module Int32) 21l 21l;;
                                      - : int32 = 42l
                                      
                                      add (module Int64) 21L 21L;;
                                      - : int64 = 42L

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

                                    • [^] # Re: Jai: Language for game programmming

                                      Posté par . Évalué à 2.

                                      Dans la série liens intéressants et instructifs : Demystifying Type Classes sur le site de Oleg Kiselyov. D'une manière générale, son site est une véritable mine d'or ! :-)

                                      Sur cette page, il y explique de manière didactique et complète le principe des type classes en étudiant trois implémentations possibles du système dans un langage : par passage de dictionnaire, par monomorphisation ou par analyse intentionnelle de type. La première étant la plus répandue et les deux secondes étant issues de l'article pionnier sur le sujet : Parametric overloading in polymorphic programming languages de Stephan Kaes en 1988 (un lien vers cet article est donné en bas de page).

                                      On y trouve aussi une comparaison avec des techniques du C++, comme il le souligne en conclusion :

                                      We have presented the intensional type analysis, the third way of compiling type classes. Whereas monomorphization is akin to C++ template instantiation and dictionary passing is similar to vtable, the intensional type analysis reminds one of resolving overloading by a large switch statement.

                                      Traduction rapide :

                                      Nous avons présenté l'analyse intentionnelle de type, la troisième façon de compiler les classes de type. Tandis que la monomorphisation est similaire à l'instantiation de template C++ et le passage de dictionnaire est similaire aux vtable, l'analyse intentionnelle de type fait penser à la résolution de surcharge comme à un grand switch.

                                      La dernière technique m'a donné une idée un peu différente de la sienne, mais largement inspirée de son illustration en OCaml, en utilisant des types sommes ouverts et des GADT. Exemple avec la fonction show :

                                      (* on définit un type somme ouvert dans lequel on peut rajouter des cas après coup *)
                                      type 'a showable = ..
                                      
                                      (* on définit la fonction show pour ce type encore vide *)
                                      let show : type a. a showable -> a -> string =
                                        fun _ x -> failwith "failed overloading resolution";;
                                      val show : 'a showable -> 'a -> string = <fun>
                                      
                                      (* on rajoute des cas dans le type avec leur instance pour show *)
                                      type 'a showable += Int : int showable;;
                                      type 'a showable += Int : int showable
                                      
                                      let show : type a. a showable -> a -> string = function
                                        | Int -> string_of_int
                                        | ty -> show ty;;
                                      val show : 'a showable -> 'a -> string = <fun>
                                      
                                      type 'a showable += Bool : bool showable;;
                                      type 'a showable += Bool : bool showable
                                      
                                      let show : type a. a showable -> a -> string = function
                                        | Bool -> string_of_bool
                                        | ty -> show ty;;
                                      val show : 'a showable -> 'a -> string = <fun>
                                      
                                      type 'a showable += List : 'a showable -> 'a list showable;;
                                      type 'a showable += List : 'a showable -> 'a list showable
                                      
                                      let show : type a. a showable -> a -> string = function
                                        | List ty ->
                                          fun l ->
                                            let rec loop first = function
                                              | []   -> "]"
                                              | h::t ->  
                                                  (if first then "" else ", ") ^ show ty h ^ loop false t
                                            in "[" ^ loop true xs
                                        | ty -> show ty;;
                                      val show : 'a showable -> 'a -> string = <fun>
                                      
                                      (* quelques exemples *)
                                      show Int 1;;
                                      - : string = "1"
                                      
                                      show Bool true;;
                                      - : string = "true"
                                      
                                      show (List Int) [1; 2; 3];;
                                      - : string = "[1, 2, 3]"
                                      
                                      (* limitations du système *)
                                      
                                      (* on définit une fonction print sur les showable *)
                                      let print ty x = print_endline (show ty x);;
                                      val print : 'a showable -> 'a -> unit = <fun>
                                      
                                      (* cela marche bien... pour l'instant *)
                                      print Int 1;;
                                      1
                                      - : unit = ()
                                      
                                      print (List Int) [1; 2; 3];;
                                      [1, 2, 3]
                                      - : unit = ()
                                      
                                      (* maintenant on rajoute les float mais sans implémentation pour show *)
                                      type 'a showable += Float : float showable;;
                                      type 'a showable += Float : float showable
                                      
                                      (* la c'est normal, dans le grand switch le cas Float n'est pas encore géré *)
                                      print (Float) 1.2;;
                                      Exception: Failure "failed overloading resolution".
                                      
                                      (* on rajoute le cas au switch *)
                                      let show : type a. a showable -> a -> string = function
                                        | Float -> string_of_float
                                        | t -> show t;;
                                      val show : 'a showable -> 'a -> string = <fun>
                                      
                                      show Float 1.2;;
                                      - : string = "1.2"
                                      
                                      (* et là : pouf, ça marche pas pour print !!! *)
                                      print (Float) 1.2;;
                                      Exception: Failure "failed overloading resolution".
                                      
                                      (* la raison en est que en OCaml lorsque l'on définit un terme,
                                       * sa définition ne doit dépendre que des termes déjà défini qui le précèdent
                                       * et donc rajouter un cas à show après avoir défini print ne change rien à son code :
                                       * il faut la redéfinir à chaque fois que l'on rajoute une instance :-/ *)
                                      let print ty x = print_endline (show ty x);;
                                      val print : 'a showable -> 'a -> unit = <fun>
                                      
                                      print Float 1.2;;
                                      1.2
                                      - : unit = ()
                                      
                                      (* au final cette implémentation n'est pas compatible avec la programmation
                                       * modulaire et les avantages des compilations séparées. *)

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

                                  • [^] # Re: Jai: Language for game programmming

                                    Posté par . Évalué à 2. Dernière modification le 14/10/16 à 13:47.

                                    Dans leur article, ils comparent leur approche avec celles faites dans d'autres langages, comme les concepts pour le C++, section qui renvoie à un article collaboratif dont un des auteurs est Bjarne Stroustrup : Concepts: linguistic support for generic programming in C++. Comme je ne suis pas abonné à l'ACM, je n'ai pas pu lire l'article, et je me demande si ce sont bien les concept que les développeurs C++ attendent et dont parle la dépêche.

                                    J'ai accès à l'article, et je confirme qu'il parle bien des même concepts que la dépêche. Si quelqu'un veut lire l'article je peux le transférer, demandez-moi par mail: shuba<at>melix<dot>org

                    • [^] # Re: Jai: Language for game programmming

                      Posté par . Évalué à 7.

                      Premièrement je suis d'accord pour dire que malgré toutes ses améliorations, C++ ne prend pas réellement de fonctions comme objets de première classe. Les lambdas et autres types d'inférences limités sont extrêmement bienvenus, mais on reste dans le domaine d'un langage impératif (avec désormais plus d'opportunités pour avoir un style fonctionnel). Je renvoie à l'article de John Carmack pour une exploration de style fonctionnel en C++. Ceci étant dit :

                      P.S: Bon, le lambda-calcul ça date de 1936, et on a du attendre C++11 pour en avoir un ersatz à lire tes exemples

                      Histoire de troller un chouïa : étant donné que Church et son thésard, un inconnu du nom de Alan… Alan… Alan T-quelque chose, sont les deux piliers qui ont permis de créer les fondations de l'informatique théorique telle qu'on la connaît, j'ai envie de dire que crâner à propos du lambda calcul « créé en 1936 »1, alors que les principes de la machine universelle de Turing, qui sont certes arrivés plus tard (ce qui est somme toute logique), ont eux été mis en œuvre dès les années 40, c'est quand même rigolo. :-)

                      Ensuite, juste pour dire, le bouquin de B. Pierce sur les types montre qu'il est parfaitement possible d'implémenter un système de typage fort à base de lambda-calcul (il propose un mini-Java comme exemple, et un mini-ML aussi).

                      Je pense que l'ajout de trucs types lambdas à Java et C++ vient surtout du fait que lorsque tu as un écosystème populaire, il faut d'abord composer avec ce que les utilisateurs veulent (et fabricants de compilateurs proposent). Jusqu'au début des années 90, les compilateurs de production étaient franchement médiocres en termes d'optimisation (à quelques exceptions près, en particulier pour les compilateurs Fortran à destination de machines vectorielles, genre les Cray-I, etc.). Du coup, avec un langage procédural genre C, on pouvait au moins donner une forme au code qui permettait d'optimiser l'utilisation des registres, et d'être proche de la machine. Pendant très longtemps, les langages de programmation fonctionnelle a été considérée comme inefficaces en termes de performances. Ça commencé à sérieusement changer lorsque les compilateurs ont commencé à proposer des optimisations très agressives, mais même ainsi, les algorithmes (géniaux) qui sont derrière l'inférence de type de ML, et ceux qui gèrent la curryfication par exemple, ont tendance à mener à de la compilation via continuation, ce qui peut être très bien ou terrible en fonction de l'implémentation (y'a un excellent bouquin d'A.Appel sur la compilation de ML avec continuations soit dit en passant). Bref.

                      Jusqu'à il y a peu, la programmation fonctionnelle était considérée comme un truc d'académicien2. L'un des rares langages procéduraux à avoir intégré des bouts de programmation fonctionnelle était Perl: il est possible de passer des blocs de code et de créer des lambdas, tout un tas de fonctions « built-in » (genre map, grep, etc.) sont directement tirées de CLISP et ses copines, … Mais c'était aussi parce que Perl était à la base optimisé pour le traitement de fichiers textuels, et que les entrées-sorties étaient ce qui était bloquant de toute manière. Donc niveau perf, l'utilisation de fonctions de haut-niveau avec un coût à l'exécution était OK.

                      Quand OCaml, Scala, Haskell, etc., on enfin émergé et commencé à prouver qu'ils pouvaient, eux aussi, proposer des performances raisonnables (pas forcément comparé à C/C++, mais au moins par rapport à Java), tout en proposant une grande puissance d'expression, on a vu Java et C# (par exemple) commencer à se doter d'aspects fonctionnels. Encore une fois, c'est entre autres parce que les développeurs/utilisateurs du langage ont vu ce que pouvaient faire les voisins et ont réclamé ces fonctionnalités.


                      1. D'ailleurs je pense que tu parles du lambda-calcul typé; si je me souviens bien, le lambda-calcul non-typé avait été proposé 5 ans auparavant mais ne « servait à rien », d'où le besoin de pousser plus loin. 

                      2. Il faut aussi dire que c'est un peu la faute des profs. Genre en France quand OCaml était enseigné y'a ~15-20 ans, la façon d'enseigner le truc était très mathématique. On faisait en pratique des maths avec un langage de matheux. Si les profs avaient proposé de faire de la prog en OCaml mais avec un aspect pragmatique (mais en gardant l'aspect fonctionnel) je pense que ça aurait dégoûté beaucoup moins de gens de l'informatique en DEUG. 

            • [^] # Re: Jai: Language for game programmming

              Posté par . Évalué à 1.

              Selon le site C++ référence, les lambda seraient apparues avec le C++11 et ne correspondent qu'à de vulgaires fonctions anonymes construisant une clôture

              Soit 3 ans avant qu'à peu près la même chose n'apparaisse en Java.
              Et je suis gentil parce que les lambdas Java sont moins riches fonctionnellement.

              • [^] # Re: Jai: Language for game programmming

                Posté par . Évalué à 2.

                Mais nous on s'en sert ! Il n'y a pas un framework ou une bibliothèque qui ne ce soit adaptée pour être plus agréable avec des lambdas. Il y a eu une vraie évolution de la bibliothèque standard pour avoir des usages pratiques.

                Autant je déplore le manque de curryfication, autant je n'ai jamais ressenti le besoin d'avoir plus de choix dans ma façon de capturer. Ajouter des options pour ajouter des options ça ne m'intéresse pas plus que ça. Avoir une API fluent pour faire de la composition c'est déjà plus utile pour ce que je fais en tout cas.

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

                • [^] # Re: Jai: Language for game programmming

                  Posté par . Évalué à 0.

                  Je ne sais pas de qui tu parles quand tu dis "nous" mais perso j'utilise pas mal les lambdas en C++ et je curryfie régulièrement avec std::bind.

                  http://en.cppreference.com/w/cpp/utility/functional/bind

                  Je sais pas comment on fait en Java mais au pire il suffit de refaire une closure à la main même si ça doit être un peu plus lourd en syntaxe (comme je trolle pas sur Java remarquerais pas que la lourdeur de syntaxe est un standard de fait).

                  • [^] # Re: Jai: Language for game programmming

                    Posté par . Évalué à 3.

                    […] comme je trolle pas sur Java […]

                    lol ?

                    Et je suis gentil parce que les lambdas Java sont moins riches fonctionnellement.

                    Pour la curryfication :

                    […] il suffit de refaire une closure à la main même si ça doit être un peu plus lourd en syntaxe […]

                    Oui c'est tout à fait possible et comme tu peux utiliser des références de méthodes ça n'est pas forcément particulièrement lourd. Au niveau de la syntaxe une lambda c'est (params) -> { body } où les paramètres n'ont pas forcément de type explicite et où il n'est pas nécessaire de mettre le body dans un bloc s'il ne contient qu'une seule instruction. Je trouve ça assez concis.

                    Pour ce qui est du fonctionnellement moins riches, le fait que ça se base sur des interfaces et que les interfaces peuvent avoir des méthodes permet des choses qui ne sont pas directement accessible en C++ comme le fait java.util.Function avec la composition par exemple (https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html).

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

                    • [^] # Re: Jai: Language for game programmming

                      Posté par . Évalué à 0.

                      […] comme je trolle pas sur Java […]
                      lol ?

                      Tout à fait, un exemple typique de prétérition.

                      […] il suffit de refaire une closure à la main même si ça doit être un peu plus lourd en syntaxe […]
                      Oui c'est tout à fait possible et comme tu peux utiliser des références de méthodes ça n'est pas forcément particulièrement lourd. Au niveau de la syntaxe une lambda c'est (params) -> { body }

                      Donc je dis "écrire une closure à la main par dessus le lambda pour pouvoir curryfier ça peut-être un peu lourd" et tu me réponds "la syntaxe de base du lambda n'est pas lourde", t'as pas l'impression qu'on parle pas de la même chose ?
                      Cela dit mon propos n'était pas une critique, juste je disais que c'était possible de curryfier en Java.

                      où les paramètres n'ont pas forcément de type explicite

                      Oui, le compilateur C++ sait les déduire aussi.

                      Et on peut même définir un lambda générique genre:

                      auto multiplier_par_deux = [](auto x) { return 2*x; }

                      Sans être obliger de déclarer une interface avec un generic autour de java.util.Function. Suivez mon regard.

                      Pour ce qui est du fonctionnellement moins riches, le fait que ça se base sur des interfaces et que les interfaces peuvent avoir des méthodes permet des choses qui ne sont pas directement accessible en C++ comme le fait java.util.Function avec la composition par exemple

                      C'est pas un exemple c'est le seul argument.
                      C'est un lot de consolation pour toutes les limitations de java.util.Function, notamment celui que je décris juste au-dessus.

                      Mais ça ne doit pas interdire de faire du java ni d'en utiliser les lambdas !

  • # Lien mort / Dead LInk

    Posté par . Évalué à 1. Dernière modification le 12/10/16 à 09:19.

    Sur le mot "Réflexion" ( https://fr.wikipedia.org/wiki/R%C3%A9flexion_%28informatique%29 )

    ET merci à les auteurs et la modo.

  • # Photo de "nuit"

    Posté par . Évalué à 1.

    Dommage pour la photo de Tromso, j'en ai une d'Oulu prise pendant la réunion ISO C++, qui aurait été plus adaptée… Elle a été prise à 23h, en plein centre ville.

    https://goo.gl/photos/Fb6RYRfYj4pymtCk7

Suivre le flux des commentaires

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