À propos de cppcon 2021

Quelques raccourcis :

Notez que le contenu de cette page est en chantier, en particulier sur le plan du formatage, car je suis débordé. Je retravaillerai le tout dès que possible, promis.

Grâce à l'Université de Sherbrooke (en particulier, grâce à Richard Fontaine du CeFTI) et au Collège Lionel-Groulx (en particulier à Patrick Lebonnois et à mes collègues du département d'informatique), j'ai le privilège de participer (virtuellement, étant donné la pandémie de COVID-19 qui sévit) à CppCon 2021. Notez que la conférence est en mode hybride cette année, mais que les circonstances font que je serai un participant (et un conférencier) en ligne.

Ce qui suit est une sorte de journal de voyage (sans la partie voyage!), avec éléments humains et éléments techniques. Au besoin, référez-vous à ce glossaire : ../../Lexique/lexique-wg21.html

Notez que c'est pour moi une semaine très, très occupée, et que j'ai assurément oublié de mentionner plusieurs rencontres. Si je ne vous ai pas mentionné, ça en dit plus sur ma mémoire défaillante que sur l'intérêt que je vous ai porté.

Pour un peu de bagage supplémentaire sur l'événement, voir :

Pour les journaux de « voyage » d'autres participants, voir :

Début du contenu technique

Je vais mettre le contenu technique dans des blocs les mettant comme celui-ci. Si vous ça ne tombe pas dans vos cordes, passez par-dessus.

Fin du contenu technique

Les jours entourant l'événement

CppCon est un très gros événement, qui se tient chaque année depuis 2014 (vous trouverez mes journaux des années précédentes sur index.html avec des noms évocateurs tels que cppcon2014.html par exemple). Je participe à l'organisation de l'événement depuis le début, mais le maître d'oeuvre est Jon Kalb, et l'autre presque maître d'oeuvre est Bryce Adlestein-Lelbach. Nous avons tenu l'événement à Bellevue, en banlieue de Seattle, pendant plusieurs années, avant de le déplacer à Denver en 2019 par besoin d'espace (l'hôtel où nous sommes maintenant est gigantesque). En 2019, nous avions plus de 1200 participantes et participants sur place; pour un colloque aussi spécialisé, c'est impressionnant.

Depuis le début, CppCon offre non-seulement des conférences (et beaucoup! Environ six ou sept en même temps de tôt le matin à tard le soir pendant cinq jours consécutifs, hormis les Keynotes auxquels tout le monde assiste) mais aussi des classes de maître, pré-conférence et post-conférence. J'en donne d'ailleurs moi-même depuis 2016. Ces classes se donnent habituellement la fin de semaine qui précède l'événement et celle qui le suit, ce qui fait de l'événement entier (classes incluses) quelque chose qui se tient sur neuf jours consécutifs.

Cette année, les classes de maître se donnent la fin de semaine avant et la fin de semaine après. Mes classes sont Managing Memory (https://cppcon.org/class-2021-managing-memory/), juste avant la conférence, et Thinking Small (https://cppcon.org/class-2021-thinking-small/), juste après la conférence. Dur sur la voix tout ça! Mais c'est amusant. J'ai eu droit à des participants européens (Angleterre, Slovénie), asiatiques (Chine), américains (côte Ouest, Chicago, Colorado). C'était costaud, mais je pense que les gens ont apprécié.

Un gros merci au passage à mes étudiantes et à mes étudiants au Collège comme à l'Université, qui collaborent avec moi quand je dois m'absenter comme ça, et qui s'adaptent aux petites acrobaties d'horaires et de format des cours qui viennent avec le fait de m'avoir comme enseignant. J'apprécie votre ouverture.

Jour 0 24 octobre

Il y a toujours une réception au début d'un événement comme CppCon. Le but visé est de briser la glace, faire en sorte que les gens se rencontrent et socialisent un peu, surtout celles et ceux pour qui c'est une première fois. Habituellement, un accueil se fait en après-midi, alors que les gens arrivent de leur avion, viennent prendre possession de certains objets offerts par les commanditaires de l'événement (un sac, du Swag, parfois des livres, des vêtemements, etc.) et saluer des gens rencontrés par le passé puis aller manger une bouchée. Le soir, un événement plus social se tient (avec alcool, mais en quantités limitées – le reste est aux frais des gens), des bouchées, une exposition d'affiches de gens ayant soumis une proposition digne d'intérêt (projets étudiants de qualité, produits qui souhaitent se faire connaître). On casse la glace, on bavarde, on s'amuse un peu avant la semaine très intense qui nous attend.

Cette année, l'événement se tient à travers Gathering (https://gather.town/app), qui nous présente les lieux comme un environnement de jeu « à l'ancienne », avec de petits avatars qui se promènent. On peut choisir notre apparence selon certains paramètres, et le contact vidéo et audio se fait automatiquement quand notre avatar est à proximité d'un autre. C'est quand même bien.

J'ai pu bavarder avec quelques personnes, dont mes amis Billy Baker (Co-Chair de LEWG) et Walter E. Brown, tous les deux toujours aussi charmants.

Ce fut un accueil humble mais sympathique, et nous avons pu constater que la mécanique de l'événement semblait pouvoir tenir la route.

Jour 1 25 octobre

J'ai un peu de difficulté à m'orienter dans la carte virtuelle (relativement grande!), mais je finis par comprendre le mécanisme. Les lieux des présentations ne sont pas affichés sur la carte mais bien dans le programme sur l'outil de préparation de l'agenda, et il faut cliquer sur la présentation pour savoir où (virtuellement) elle se tient. Je croise Sy Brand et Céline Dedaj, toujours aussi sympathiques tous les deux, et on échange des gentillesse quelques secondes avant que je ne reprenne mon chemin.

Jon Kalb explique la mécanique. Les vidéos ne démarrent pas automatiquement pour la présentation (il faut appuyer 'x' pour que ça fonctionne). Les sessions de fin de journée seront enregistrées et diffusées à nouveau le lendemain matin pour les gens sur la côte Est. Tout sera enregistré, et on y aura accès par Zoom pendant la conférence.

Céline Dedaj présente le code de conduite, et indique où celui-ci peut être consulté. Bien fait, court, efficace. Une équipe de responsables (tous des chics types) est disponible à titre de soutien, certains sur place et d'autres en ligne.

Truc nouveau (et étrange) : on a droit à une publicité de l'un des commanditaires. C'est quelque chose de compréhensible, je suppose.

Jon Kalb avoue avoir eu une très difficile année, mais se dit fier d'accueillir Kevlin Henney.

Vidéo : à venir

Kevlin Henney – Six Impossible Things

Kevlin Henney explique que son titre vient d'Alice au pays des merveilles. Il s'intéressera aux limites de ce qui est faisable / connaissable et de ce qui ne l'est pas. Il procédera par un décompte :

6 - Les représentations peuvent être infinies (il parle de +Inf, de NaN, etc.). Il rapporte des accidents dans des voitures autonomes résultant d'une erreur d'initialisation menant à un NaN par exemple. Il parle de Batman Bugs (NaN NaN NaN NaN... 🙂).

Il cite Chuck Allison qui dit Floating Point Numbers Aren't Real (les réels ont une représentation infinie, pas les nombres à virgule flottante)

Kevlin Henney nous rappelle que l'histoire est pleine de bogues informatiques qui résultent de choses qui ne devaient jamais se produire... Il rappelle au passage Jon Bentley et l'erreur historique de son algorithme de recherche dichotomique... dans un article nommé Writing Correct Programs! C'est intéressant : Kevlin Henney montre la démarche de Jon Bentley qui met en relief les invariants, et construit une preuve... When writing a program, the hard parts work first; the bugs are ine the simple parts, those that are overlooked because they are « simple ». Kevlin Henney rappelle ensuite de Joshua Bloch a implémenté cet algorithme en Java, et que ça a fonctionné... jusqu'à ce qu'on l'applique à de très gros tableaux. Les entiers ne sont pas infinis non plus, du moins quand on doit les écrire! Kevlin Henney nous rappelle que la pensée abstraite, indépendante du substrat, est utile, mais a ses limites et peut être dangereuse. On suppose des choses qui peuvent ne s'avérer que dans l'abstraction, pas dans le concret.

Kevlin Henney en profite pour montrer à quel point de type de problème devient simple à résoudre correctement avec C++ 20 🙂

Kevlin Henney relate ensuite un navire militaire paralysé dû à une division par zéro qui a paralysé son réseau. C'est un autre enjeu de gestion de l'infini...

5 - Toute question a une réponse.

Kevlin Henney parle de voyage dans le temps, résultant de débordements dans la représentation du temps en termes de secondes, ou d'une initialisation à zéro malvenue qui ne ramène au « début des temps » (p. ex. : 31 décembre  1969 🙂). Il mentionne un cas où le bogue pourrait tenir au fait qu'un fuseau horaire aurait été mal géré, ou à un bogue de seconde intercalaire. Il se trouve qu'en C, si un calendrier est indisponible, certaines fonctions retournent... ((time_t)-1) en pratique! L'erreur est qu'on suppose que la réponse à la question « quelle heure est-il? » est disponible.

Kevlin Henney mentionne <algorithm> et indique (intéressant) que C++ ne définit pas ce qu'est un algorithme. L'en-tête <algorithm> spécifie des complexités algorithmiques, des effets, mais pas des algorithmes.

 

(note : j'ai manque le titre du 4)

 

Kevlin Henney parle de tris. Permutation Sort a une complexité factorielle! Évidemment, bogosort est pire... En fait, il n'est pas clair que bogosort qualifie en tant qu'algorithme, du fait que sa complexité est indéfinie et qu'il peut ne pas terminer.

Kevlin Henney cite Edsger W. Dijkstra qui nous disait que les tests peuvent démontrer la présence de bogues, pas leur absence. Point intéressant : tester is_sorted(seq) suivant sort(seq) ne garantit pas que la séquence soit triée, car un bogue qui remplacerait le contenu de la séquence par une série de valeurs identiques ne serait pas détecté.

Kevlin Henney aborde la question du temps d'exécution, pour aller vers le problème de l'arrêt (Halting Problem). On trie une séquence courte; on se dit « après une seconde, si c'est pas fini, il y a sûrement un bogue », mais est-ce vrai? Kevlin Henney parle de Kurt Gödel et de son célèbre théorème d'incomplétude, qui nous rappelle que nous ne saurons pas tout. Il parle aussi de Gödel, Escher Bach (livre délicieux) qui nous rappelle que toutes les formulations axiomatiques de la théorie des nombres contiennent des propositions indécidables.

Kevlin Henney aborde l'écriture de std::strlen(const char *s) où on cherche '\0' dans une séquence de bytes. Quelles en sont les préconditions? Que s!=nullptr bien sûr, mais aussi que '\0' soit atteignable (Reachable) à partir de s, ce qui est indécidable de l'intérieur du système! Il montre des cas triviaux qui peuvent le briser, comme un char[5] contenant "bogus" ou un s non-initialisé. Il n'y a pas de comportement indéfini dans la machine; c'est un artéfact de la spécification du langage.

Kevlin Henney critique les efforts sur les contrats (de C++ 23 ou C++ 26) au passage, sur la base de l'utilité de ce que cela permettra de représenter.

Kevlin Henney rapporte une expérience amusante : il a pris 99 téléphones Android, et a pris une marche dans les rues de sa ville... Google Maps a présenté cela comme du bouchon de circulation! Il trace des parallèles avec les « J'aime » et les retweets, qui ont une valeur discutable.

3 - Le futur peut être connu avant de se présenter. Kevlin Henney cite Grace Hopper et sa vision de la programmation comme une investigation gigantesque de la connaissance humaine. Il parle des connus connus, des connus inconnus, des inconnus connus et des inconnus inconnaissables. Il nous rappelle notre tendance à estimer moins importantes les choses que nous ne connaissons pas (si c'était important, on le connaîtrait, non?).

Kevlin Henney discute de l'importance du « être là » pour comprendre le système, et revient sur son exemple de cellulaires dans une malette.

Kevlin Henney poursuit avec une discussion des nuances entre Precision et Accuracy, pour aborder la question de la difficulté de prévoir les coûts d'un projet. Livrer un estimé trop précis dans un domaine qu'on ne maîtrise pas assez est ... faux. Les Roadmaps sont aussi une représentation nuisible selon lui (quel est l'intérêt s'il n'y a qu'un seul chemin?). Kevlin Henney indique (et il a raison) que nous avons eu quelques pandémies au cours des derniers vingt ans, mais qu'on ne les voit jamais planifiées dans les calculs du temps requis pour un projet. Un terme comme Business Value n'a pas une définition univoque de toute manière, alors prioriser sur cette base ne fonctionne pas.

Kevlin Henney montre son horloge, qui tourne en sens anti-horaire :)

2 - Un système distribué est compréhensible (Knowable).

Kevlin Henney aborde la question du théorème CAP (Consistency, Availability, Partition Tolerance : on peut en avoir deux sur trois, pas trois), et cite Leslie Lamport qui nous rappelle que dans un système réparti, un noeud que nous ne connaissons pas peut rendre le système entier inopérant. Kevlin Henney montre que certaines incohérences peuvent être acceptables dans un système réparti mais inacceptables dans un client qui n'a que son propre espace adressable.

1 - La dette technique est aussi quantifiable que la dette financière.

Kevlin Henney explique que la dette technique n'est pas la même chose que la dette technique incontrôlée. En particulier, une hypothèque est une dette contrôlée. Une dette technique incontrôlée est une chose dont on n'est pas conscient, où le programme évolue d'une manière telle que sa structure en souffre. On parle de Code Rot, Code Decay, code spaghetti... On doit le terme « dette technique » à Ward Cunningham, qui expliquait les enjeux techniques à des individus du monde de la finance. Ward Cunningham mettait entre autres en valeur qu'on paie des intérêts dans les deux cas.

Kevlin Henney poursuit en mentionnant que la quantification de la qualité logicielle ne repose pas sur des bases formelles solides. Nous estimons informellement les coûts logiciels, mais nous ne sommes pas en mesure de les quantifier véritablement. Souvent, ces estimés sont exprimés en temps, pas en coûts financiers. Son article sur le sujet est https://www.oreilly.com/radar/on-exactitude-in-technical-debt/

0 - Kevlin Henney prend les questions. Ça va vite et je ne parviens pas à noter le tout. L'une d'entre elles porte que la dette technique de C++ et de ce que WG21 essaie de faire pour s'en occuper. Kevlin Henney pense que la dette est massive dû à l'accumulation des années, mais qu'on a en place des mécanismes de dépréciation et de remplacement alors on ne parle pas d'immobilité. En retour, il y a des millions et des millions de lignes de code C++ dans le monde et prendre cette dette en charge dépasse largement les capacités de WG21.

(autres trucs entendus : le Lisp le plus élégant est Lisp 1.5; le premier Smalltalk a été écrit en Basic – je ne suis pas sûr si j'ai bien compris; à valider)

C'était une excellente présentation (techniquement, c'était le premier de six Keynotes en cinq jours!). Nous avons droit à un autre Keynote dans quinze minutes, par Bjarne Stroustrup celui-là.

Entre les présentations, je reste sur place et j'apprivoise le système de Gathering. C'est un peu étrange : on voit les visages des gens près de nous et on les entend, mais ce n'est pas transitif (les gens près de mon voisin ne sont pas nécessairement près de moi), alors c'est un peu comme écouter des conversations d'inconnus...

Quand on se connecte à la salle où l'événement se tient physiquement, on a le band Rock habituel de CppCon qui nous accueille. Depuis le début, nous avons toujours eu ce band Blues + Rock pour nous accompagner en périphérie des Keynotes, quand nous sommes tous ensembles; cela fait partie de la signature de l'événement.

Nous avons encore une publicité (la même...)

Bjarne Stroustrup a un petit retard, mais on l'accueille avec plaisir. Jon Kalb le présente, et se dit ravi d'être en personne. Les badges servent de preuve de vaccination (pas de vaccin, pas d'accès). Il y a plus de 250 personnes sur place, et plus de participants qu'en 2019 si on compte les participants en ligne. Le WiFi est minimal (si quelqu'un sur place souhaite profiter d'un WiFi plus puissant, il est préférable de le faire de sa chambre). Discord est l'outil de communication privilégié. Céline Dedaj présente à nouveau le code de conduite pour celles et ceux qui n'auraient pas assisté à la présentation de Kevlin Henney.

Vidéo : https://youtu.be/15QF2q66NhU

Bjarne Stroustrup – Reaching for the Aims of C++

Bjarne Stroustrup discute d'évolution, de changements, d'adaptation , d'objectifs, de croissance... Je ne note pas tout car c'est une histoire que je connais bien, mais je vous recommande de regarder la présentation quand elle sera en ligne. On y voit les gens, les lieux, les idées qui ont donné forme à ce langage.

Bjarne Stroustrup rappelle les critiques du début, par exemple quand il a introduit les prototypes de fonctions (en C, on pouvait en faire l'économie... Brrr...), et discute des difficiles enjeux associés à la compatibilité entre les langages.

Parmi les idées clés de C++, qui étaient novatrices à l'époque, on trouve représenter les idées à même le code.

Bjarne Stroustrup relate l'introduction de RAII, en 1979. Il rappelle les noms utilisés, l'historique de la définition, la source de ce précieux concept. Il rappelle que Compilers don't read manuals, and most programmers don't either pour expliquer l'intérêt d'une stratégie générale de gestion des situations atypiques pour la gestion des ressources. Cela a mené à un formalisme pour l'affectation, des efforts pour réduire les coûts des copies (RVO en 1982; la sémantique de mouvement en 2011, etc.).

Bjarne Stroustrup aborde l'introduction du support de la surcharge des opérateurs pour permettre l'expression d'idées riches pour les types maison autant que pour les types fondamentaux. Ensuite, il aborde l'idée de la généricité (1980!) pour exprimer des conteneurs génériques, particulièrement std::vector<T>.

Bjarne Stroustrup rappelle que la POO était très critiquée à l'époque. Il considère avoir intégré ce paradigme à ceux supportés par C++ en 1984, mais c'était à l'époque vu comme un paradigme pour experts seulement.

Bjarne Stroustrup rappelle l'histoire de son excellent livre D&E, et des principes qui y sont énoncés.

Ensuite, Bjarne Stroustrup aborde la question du Liftoff From C en 1998. Qu'est-ce qui a fait le succès de C++ à l'époque? Une capacité de gérer des projets plus complexes, écrire de meilleures bibliothèques, exprimer des idées selon plusieurs paradigmes, etc., mais le tout sans perdre de « performance ». Il dit qu'on a beaucoup critiqué son manque de « pureté », son intérêt pour la stabilité et la compatibilité avec C.

Bjarne Stroustrup présente la courbe du nombre d'usagers de C++, qui a baissé au début des années 2000 avec la popularité de Java et l'arrivée de C#, incluant l'intérêt des systèmes pris en charge, puis a repris très fort vers 2005 quand il est devenu évident que ces langages ne permettent pas de profiter du matériel convenablement. Bjarne Stroustrup soulève la question importante à savoir si C++ 20 permettra un Liftoff from C++ 11.

Pour le reste de la présentation, Bjarne Stroustrup aborde la question des objectifs : bien définir des objectifs, gérer la complexité, simplifier la réalisation des tâches simples sans nuire à la réalisation des tâches complexes. Il suggère (quand c'est possible) de viser l'expression de « ce que je veux faire » plutôt que du « comment je veux le faire ». Il rappelle les principes de C++, par exemple :

(j'ai dû quitter quelques minutes; mon plus jeune, Ludo, se plaint de maux de ventre alors je dois aller le cueillir à l'école)

Je reviens et Bjarne Stroustrup parle de composition de concepts, un merveilleux mécanisme. Il disserte sur la relation entre les concepts et les types, et se dit d'avis que les concepts sont plus importants que les types dans les signatures de fonctions. Il constate que certains mécanismes sémantiques (p. ex. : les axiomes) demeurent à intégrer au langage.

Bjarne Stroustrup aborde la POO et dit qu'il nous manque encore aujourd'hui deux idées clés de D&E, soit les multiméthodes et le Uniform Call Syntax.

Bjarne Stroustrup prend le blâme pour la confusion quant à la gestion des erreurs et le choix des bons mécanismes en fonction des situations. Il pense entre autres que les gens sont trop émotifs sur ce sujet, et que nous avons trop d'alternatives. Il présente (sagement) cet enjeu comme dépendant du domaine et des besoins; ce n'est pas un point à exprimer en termes de noir et blanc. Les exceptions brillent quand les problèmes sont rares, et dans les cas (opérateurs, constructeurs, etc.) où un canal d'erreur explicite sera inapproprié.

Bjarne Stroustrup retrace l'importance du parallélisme et de la concurrence (incluant les coroutines) dans l'évolution de C++. Les coroutines étaient dans le collimateur dès le début; c'est la résistance de l'industrie qui a ralenti son avènement (la toute première bibliothèque de C++ était une bibliothèque de tâches). Certains algorithmes parallèles manquent (find_all() par exemple).

Bjarne Stroustrup fait un plaidoyer en faveur des analyseurs statiques, pour distinguer le « bon » et le « recommandable » du « légal ». Il en profite pour discuter des Core Guidelines.

Q : peut-on envisager des valeur réellement immuables ou de la programmation par continuations?

Bjarne Stroustrup : je n'ai pas de réponse générale. On peut le faire en C++, mais pour le standardiser il faut voir comment l'intégrer au tout.

Q : que devrait-on bannir?

Bjarne Stroustrup : peut-être pas bannir, mais je pense qu'on a un ensemble solide de Major Features; chacun peut avoir des côtés délicats. Certaines conversions implicites (p. ex. : de int à char) me semblent périlleuses; une conversion explicite serait sans doute préférable. La syntaxe déclarative de C n'est pas ma préférée; j'aime bien la syntaxe vers laquelle nous mène template<auto>

Q : à quand un nouveau A Tour of C++?

Bjarne Stroustrup : il y en aura un. Idem pour le Swan Book. Je ne sais pas quand : j'aimerais commencer par import std; puis discuter de coroutines en détail. Pour le gros livre, The C+ Programming Language, je ne pense pas faire cela à court terme. C'est un solide deux ans de travail à temps complet et je ne suis pas certain que ce serait le bon médium aujourd'hui.

Q : en quoi optimiser le code dans les années '80 différait-il de faire cette tâche aujourd'hui?

Bjarne Stroustrup : à l'époque, on pouvait comprendre le matériel en détail. C'est plus subtil et plus mouvant aujourd'hui. Les principes de base demeurent : ne consomme pas trop de mémoire, et quand tu en consommes, fais-le dans un ordre qui favorise le travail du matériel.

Q : comment pourrait-on accélérer l'évolution du langage?

Bjarne Stroustrup : je ne suis pas sûr de vouloir faire cela.

Q : quelle est la relation entre C++ et les autres langages (évolution des mécanismes, interopérabilité)?

Bjarne Stroustrup : on se tient au courant des changement dans les divers langages, mais il peut y avoir un long moment entre l'avènement d'un mécanisme et son adoption, ce qui complique le repérage de son héritage conceptuel.

Q : je comprends que vous n'aimez pas beaucoup le préprocesseur. Qu'en est-il de la génération du code par le code?

Bjarne Stroustrup : la première clé est la réflexivité statique. On a déjà les templates, constexpr, consteval... Pour un mécanisme plus général, je pense que ça ira à C++ 26

Q : je veux vous remercier. Les valeurs que vous promulguez sont aussi les miennes. Je les ai appris de vous; vous me rendez fier de faire ce que je fais.

(Bjarne Stroustrup est touché)

Q : peut-on étendre la gamme des algorithmes standards pour mieux supporter certains domaines de pointe comme le Machine Learning?

Bjarne Stroustrup : C++ est très présent dans ce domaine. Je ne pense pas que WG21 pourra faire quelque chose à court terme (ça bouge trop vite!), mais la communauté le peut. Michael Wong travaille là-dessus et présente quelque chose à CppCon cette année si vous êtes intéressé

(la question suivante amène l'enjeu de la diversité des outils dans la bibliothèque standard, mais C++ est un effort bénévole, pas un produit)

Fort intéressant tout ça. J'ai quelques secondes pour me faire à déjeuner, puis je me déplace (avec difficulté, mais je pense avoir compris comment m'orienter... enfin!) vers la présentation de mon ami Jason Turner.

Vidéo : à venir

Jason Turner – You New Mental Model of constexpr

Jason Turner utilise une police 8 bits 🙂

Jason Turner propose une description de constexpr : l'acte de déplacer des calculs de l'exécution vers la compilation. Pour lui, ce n'est pas de la métaprogrammation

Jason Turner un programme constexpr ne se modifie pas lui-même comme les métaprogrammes génériques manipulent les types par exemple

Jason Turner revient sur l'historique de constexpr et ses limites, de même que sur leur graduelle réduction. Il fait remarquer que les λ sont les seuls éléments de C++ 17 qui sont implicitement constexpr... et avec C++ 20, les vannes sont ouvertes! (Jason Turner blague sur le terme « constexpert » :) ).

Jason Turner poursuit : puisqu'on peut faire à peu près n'importe quoi à la compilation maintenant, comment devrait-on penser constexpr désormais?

Jason Turner : il y a un continuum, où entre 0% et 100% du programme peut être exécuté à la compilation. Ça va de :

static constexpr auto start = 15;
static constexpr auto end = 15;

... à plus avancé, p. ex. : CTRE (les Compile-Time Regular Expressions), qui sont massivement plus rapides que std::regex

... à un peu drôle, comme le Ray Tracer à la compilation de Charles Giessen

... à un émulateur ARM qui est constexpr-enabled (par Jason Turner lui-même)

Q : on peut faire n'importe quoi, mais que devrait-on faire?

Jason Turner : ça dépend du domaine d'application. Plusieurs pensent que leur code ne se prête pas à constexpr, mais quand on y pense... (vector, string). Il montre un PETSCII qui est constexpr 🙂 Une table de conversions de string à string (une fonction qui prend une séquence de string, crée une table de PETSCII et la retourne). Une table embarquée de Bitmap Fonts. Une table de Scaling de polices à la compilation (il montre quelques applications chouettes). Il se trouve que ses polices 8 bits sont ce qu'il génère!

Jason Turner poursuit avec une génération de diapos à la compilation (oh, je pense que c'est une présentation très méta 🙂). Et en fait, sa présentation tient sur 32 Ko! Sur ces 32 Ko, 17 Ko sont des tables générées à la compilation. Et toute sa présentation roule sur un Commodore 64, compilée en C++ 20. Ce qui l'a arrêté est l'espace mémoire disponible. Il aurait pu aller plus loin en compressant / décompressant le tout... mais une autre présentation à ce sujet se tiendra à CppCon aujourd'hui!

Jason Turner poursuit en générant des codes QR à la compilation. Il nous donne une URL pour voir le code : https://tiny.url/constexpr2021

Q : pourquoi le compilateur n'applique-t-il pas constexpr pour nous?

Jason Turner : parce qu'il y a un contrat moral entre le compilateur et le programmeur... mais en pratique, je n'ai jamais eu à retirer constexpr d'une fonction dans ma pratique, alors je suis de moins en moins certain que c'est un argument qui se tient.

Pour le reste de la présentation, Jason Turner montre comment il fait réaliser de plus en plus de code à la compilation, en déplaçant du code dans une λ constexpr graduellement... Le code fond. Il va presque jusqu'à un affichage à la compilation : il génère le tampon de données à afficher à la compilation; à la fin, il ne lui reste qu'un appel à blit() pour cracher les données à l'écran physique.

Le code final comprend... six instructions assembleur!

Q : si tu ne connaissais pas les intrants, devrais-tu déplacer les efforts à l'exécution?

Jason Turner : bien sûr, mais j'aurais écrit les mêmes fonctions. Dans un cas comme celui-ci, où je présente des diapos (donc où le texte est connu a priori), cela dit, il n'y a que des bénéfices

(je passe quelques questions qui m'intéressent moins)

Q : est-ce que tu profites du fait que constexpr empêche le comportement indéfini?

Jason Turner : oui, mais il y a une autre présentation à CppCon cette année où les gens ont déplacé un micronoyau de système embarqué vers constexpr pour démontrer qu'il ne contient pas de comportement indéfini 🙂

J'ai adoré celle-là; méta et tout, brillant!

Je profite des quelques moments entre les présentations pour laver un peu de vaisselle...

Vidéo : à venir

Vittorio Romeo – C++11/14 at Scale: What Have We Learned?

Vittorio Romeo a un peu de retard, mais on l'attend sagement. Je ne gère pas une grande entreprise (c'est le moins qu'on puisse dire!), mais je suis curieux de voir ce que quelqu'un comme lui (qui travaille pour Bloomberg si je me souviens bien) a à raconter à ce sujet. Vittorio Romeo nous indique d'ailleurs que cette présentation se base sur le contenu d'un livre qu'il est en train d'écrire (et qui sera publié sous peu).

Vittorio Romeo raconte qu'il offre de la formation en C++ chez Bloomberg depuis 2017. Il souhaite expliquer les impacts d'introduire les mécanismes de ce langage à l'échelle d'une compagnie comprenant plus de mille programmeuses et programmeurs.

Vittorio Romeo prend quelques minutes pour discuter de l'intérêt d'étudier C++ 11 et C++ 14 en 2021. Il montre les statistiques d'utilisation connues, et il s'agit clairement du standard utilisé par le coeur de la communauté. Le fait que l'adoption des standards soit un peu lente tient entre autres au fait que plusieurs doivent tenir à jour du logiciel vieillissant.

Vittorio Romeo fait remarquer (c'est intéressant) que plusieurs ressources pour apprendre C++ présentent les mécanismes, mais négligent l'expérience.

Vittorio Romeo aborde les petites choses de C++ 11 et C++ 14 qui l'ont surpris dans sa redécouverte. Le fait que l'on n'ait plus à séparer les > > par une espace pour fermer une paire de chevrons dans un cas de templates. En pratique, cela a brisé certains programmes, comme :

template <int POW_OF_TWO> struct PaddedBuffer{ /* ... */ };
PaddedBuffer<256 >> 4> buf; // Ok avant C++11 seulement

... qui requiert maintenant des parenthèses. Son point est que même des très petits changements peuvent porter à conséquence. Il en mentionne d'autres (p. ex. : l'encodage du code source et des blancs dans un littéral texte brut)...

Vittorio Romeo passe une bonne partie de sa discussion expliquant comment il procède pour préparer et offrir les formations qu'il offre chez Bloomberg.

Vittorio Romeo poursuit un parlant de l'impact de ces enjeux sur la rédaction de guides de styles. Il se questionne sur l'intérêt et les choix esthétiques des rédacteurs. Il discute aussi de mécanismes qui, même s'ils ne sont pas dangereux en soi, tendent à amener les gens à écrire du code plus dangereux. Lui et John Lakos essaient de mettre en place une métrique de sécurité de mécanismes; ils catégorisent les mécanismes en :

Même les mécanismes Safe sont parfois porteurs de danger, p. ex. : override est utile, mais optionnel, alors si on dépend sur ce mot clé il est possible d'entretenir des présomptions fausses sur le code source. Heureusement, selon leur catégorisation, la majorité des mécanismes sont Safe ou Conditionally Safe.

Vittorio Romeo cite Kate Gregory et recommande de viser, à « performance » égale, l'outil le plus simple pour atteindre son objectif.

Vittorio Romeo fait une étude de cas avec les qualifications friend étendues de C++ 11... mais il manque de temps et doit s'arrêter.

C'était intéressant, mais j'aurais préféré qu'il mette plus de temps sur le volet contenu que sur l'expérience d'enseignement; ça aurait été plus pertinent pour moi.

Vidéo : à venir

Bjarne Stroustrup – Type-and-resource safety in modern C++

Bjarne Stroustrup situe cette présentation autrement que celle de ce matin, qui proposait une perspective historique. Il souhaite présenter des travaux contemporains.

Bjarne Stroustrup dit viser une sécurité (types et ressources) complète, à partir de techniques de programmation judicieuses, de bibliothèques de qualité et d'analyse statique, le tout sans coûts à l'exécution. Il base cette présentation sur https://www.stroustrup.com/resource-model.pdf

Bjarne Stroustrup distingue sécurité et Type-Safety (un programme Type-Safe peut être très dangereux). Il distingue aussi « légal » de « sécuritaire » ou de « facile d'entretien ». Il mentionne bien sûr les Core Guidelines et leur instrumentation par des outils contemporains, mais il est conscient qu'aucun outil ne peut tout faire pour un programme de complexité arbitrairement grande. Enfin, il n'abordera pas tous les bogues possibles et envisageables (concurrence, opérations signées / non-signées, conditions de course, etc.)

Bjarne Stroustrup revient sur l'idée d'encoder les idées à même le code, mise de l'avant ce matin. Il dit que certaines manoeuvres non-vérifiables ne font pas partie de son analyse; l'analyse statique doit être locale, le raisonnement doit être local. Les DLL compliquent le portait... Un système d'analyse statique qui aurait trop de faux-positifs serait rejeté par ses usagers.

Bjarne Stroustrup parle de la recommandation d'initialiser tous les objets (et de les déclarer au point de première utilisation si possible). Il donne des exemples, tout en acceptant les compromis faits (localement) pour des raisons d'efficacité. Il poursuit en parlant de ne pas laisser fuir de ressources. De ne pas laisser de pointeurs qui ne pointent pas sur un objet. Bien sûr, il montre les outils de C++ pour éviter ces écueils (dans la bibliothèque standard comme dans la GSL). Il met l'accent de manière récurrente sur un aspect : si un aanlyseur statique ne peut prouver qu'une manoeuvre est convenable, elle devrait être rejetée (j'ai un malaise; j'ai beaucoup de faux-positifs en pratique).

Bjarne Stroustrup aborde les annotations gsl::owner. Ces annotations signifient « a le droit de détruire cet objet ». Toutefois, annoter le code devient lourd et Bjarne Stroustrup recommande d'utiliser des objets clairement responsables comme std::unique_ptr<T> ou std::vector<T>. Il montre une technique par laquelle ces entités annotent les objets à l'interne. En pratique, plusieurs analyseurs statiquent supposent qu'une méthode non-const invalide l'état de son objet, mais cela pose problème avec operator[] alors il faut trouver une manière d'annoter ces cas particuliers.

Bjarne Stroustrup présente les conteneurs comme des Memory Pools en pratique, et discute des enjeux (en particulier, la complexité de détecter un cycle dans un graphe). Il suggère de séparer (conceptuellement) « posséder / être responsable pour » de « pointer ». Il poursuit avec une discussion des tests de bornes d'un tableau, d'un span<T> ou d'un vector<T>.

Note personnelle : j'espère que certains de mes étudiants de Managing Memory sont ici car Bjarne Stroustrup recoupe plusieurs parties de mon discours de samedi après-midi!

Bjarne Stroustrup parle du code de bas niveau et de possibles annotations comme [[trusted]] pour permettre sa rédaction, tout en n'encourageant pas cette pratique.

Bjarne Stroustrup explique ensuite pourquoi des idées comme celles des Core Guidelines ne sont pas intégrées à même le langage; il y a trop de réalités différentes pour justifier imposer ces règles partout. Il se dit d'avis qu'il faille d'abord et avant tout relever la qualité générale du code produit.

Bjarne Stroustrup termine par un appel à tous dans l'espoir de voir des avancées dans le domaine de l'analyse statique.

Q : vous recommandez d'étiqueter le code trop complexe. Y a-t-il une technique plus formelle / plus automatique?

Bjarne Stroustrup : je ne sais pas à quel point une analyseur statique devrait être astucieux. J'espère des suggestions de gens qui contribuent à l'analyse statique. Je ne pense pas qu'on doit standardiser tout cela, mais certaines idées de base ici pourraient être standardisées selon moi.

Q : depuis des années, je donne des conseils à mes clients à propos de pointeurs invalides. Plusieurs réinitialisent ces pointeurs à nullptr pour que cette invalidation soit claire, mais je leur recommandais de ne pas s'en occuper. Je ne suis plus certain...

Bjarne Stroustrup : on ne met pas nullptr pour la sécurité; on initialise les objets dès leur construction ou on les déclare non_null<T>

Bjarne Stroustrup mentionne ne plus avoir confiance, du moins à court terme, aux contrats pour traiter ces situations.

Q : vous parlez de bannir certaines conversions implicites. Devrait-on introduire des mots clés pour cela?

Bjarne Stroustrup : au niveau des compilateurs, ça ne me semble pas viable. Il y a trop de code « en liberté » qui en dépend

Q : devrait-on tous appliquer les Core Guidelines?

Bjarne Stroustrup : les analyseurs ont des profils pour choisir des règles dans la liste, mais ça ne me semble pas avisé. Le cadre dans son ensemble est porteur de sens; piger à la pièce m'apparaît contre-productif.

Q : est-ce que les types de retour covariants supporteront les types valeurs tels que unique_ptr<T>?

Bjarne Stroustrup : je ne suis pas un fan des types de retour covariants. Si quelqu'un les souhaite, il faudra les proposer, et je m'opposerai probablement.

J'ai quinze minutes alors je fais un petit souper rapide à mes enfants Vayda et Ludo, puis je reviens à mon poste pour la dernière présentation de la journée.

Vidéo : à venir

Titus Winters – Designing for the Long Term: Invariants, Knobs, Extensions, and Hyrum’s Law

Titus Winters se présente. Il dit que nous avons en C++ des outils extrêmement expressifs pour exprimer (justement) nos intentions, mais que nous permettons aussi au compilateur de voir à travers ces outils pour réaliser des optimisations. Il parle d'exposer des abstractions, par exemple ajuster le taux de croissance de capacité d'un std::vector<T> en fonction du rapport de coûts entre le processeur et la mémoire (accroître ou réduire).

Titus Winters dit que d'autres préfèrent améliorer le code à travers la refactorisation. Il mentionne que le principe de Pareto ne s'applique pas autant (selon les mesures qu'il a faites au fil des années) que l'on pourrait le croire.

Titus Winters dit qu'il importe de procéder de manière cohérente. Si tout le monde ajoute sa string_view maison au code de la compagnie, il devient difficile de vraiment améliorer un produit. Bad Abstractions lead to Bad Optimizations. Il poursuit en indiquant que la configuration est une abstraction. Plus on donne du contrôle aux utilisateurs (et ceux-ci en demandent!), plus on perd le contrôle sur l'optimisation... ça fait parfois mal, surtout si l'interface peut demeurer longtemps.

Titus Winters demande quelle est la taille optimale pour le tampon de réception d'un socket. La bonne réponse varie au fil des années; donner le contrôle aux usagers peut ne pas être la bonne solution ici. Il y a un équilibre à trouver.

Titus Winters introduit trois types de contrôles : des fanions, de la configuration, des extensions. Mais avant de procéder, il décrit sa philosophie quant au développement logiciel : un mécanisme peu utilisé est un risque (p. ex. : les usagers comprennent les cas habituels, on peut oublier cette possibilité de configuration, moins bien l'entretenir...); détecter les problèmes à la compilation est bien.

Titus Winters parle des interfaces de la vie courante. Par exemple, un robinet (où est le chaud / le froid?), où notre configuration nous permet de dire ce qu'on veut, mais ça nous amène vers un éventuel état stable (on ne l'a pas tout de suite). Un thermostat aussi. Ce sont des produits qui sont « orientés résultats ». Un micro-ondes est un peu différent, mais comporte des boutons « pré-programmés » comme « pop-corn », or les vendeurs indiquent souvent sur les sacs de maïs soufflé de ne pas utiliser ce bouton! L'enjeu est de le faire manuellement (je sais mieux que l'appareil quoi faire) ou automatiquement (c'est suboptimal, mais ça va peut-être s'améliorer). La sortie est : peut-être que j'ai confiance envers les gens qui amélioreront la situation.

Titus Winters revient aux compilateurs. On tend à utiliser des fanions « grossiers » comme -O3 ou -Wall plutôt que des fanions « à la piece », très granulaires, que ce soit pour l'optimisation ou pour les diagnostics.

Titus Winters poursuit avec la configuration de tampons dans une bibliothèque qui fait des entrées / sorties, synchrones ou asynchrones. Il décrit l'interface et... c'est pas super homogène. Il se trouve que l'interface a grandi « organiquement » au fil du temps, ce qui explique son état actuel. Titus Winters suggère que si nous avions demandé à l'usager (avec un enum, par exemple) pour quels cas optimiser, les résultats auraient probablement été meilleurs. Ou encore, demander à l'outil « dis-moi donc à partir de quel seuil ce serait trop » (note : ceci rejoint une de mes propositions).

Titus Winters parle des évolutions matérielles et des occasions d'optimisation qui changent. Si la personne responsable de l'entretien de la bibliothèque doit honorer l'intention (le but), elle est plus à même d'en profiter. Exposer une interface plus granulaire tend à exposer des détails d'implémentation. Titus Winters insiste aussi sur le fait qu'il est difficile de comprendre les intentions des clients qui utilisent toutes ces options qui leur sont proposées (pourquoi ces choix et pas d'autres?), ce qui complique l'évolution du système.

Titus Winters indique que ce ne sont pas tous ces petits points de contrôle qui sont mauvais. Il faut toutefois qu'ils aient une valeur sémantique.

Titus Winters dit avoir vu des enjeux de configuration tels que ceux-ci en termes de migration logicielle. Par exemple, adapter les comportements par défaut d'un programme, ou adapter la visibilité par défaut des modules à compiler (chez Google, ça a été compliqué, alors ils sont allés en deux temps -- c'est une approche intéressante : introduire une valeur par défaut, legacy:public, puis permettre aux gens de l'adapter).

En résumé, la configuration devrait être minimale, permettre de raisonner sur le plan sémantique, de granularité grossière et orientée vers les objectifs. On peut expérimenter sur un Framework destiné à cette fin avant de procéder, pour voir venir les coups.

D'autres aspects explorés :

Titus Winters conclut en discutant de la difficulté de définir des interfaces correctes, et du combat entre l'optimisation et la personnalisation.

Q : est-ce que les usagers qui n'utilisent pas la configuration par défaut d'une API ne devraient pas tout simplement utiliser une autre API?

Titus Winters : peut-être; faudrait y réfléchir et faire un peu de recherche

Q : il y a beaucoup de configurations complexes pour des Caches. Y a-t-il un bon design?

Titus Winters : je n'en ai vu que des mauvais jusqu'ici 🙂

Q : les interfaces génériques ont beaucoup d'avantages, des interfaces plus ciblées aussi. Comment choisir?

Titus Winters : fais la chose la plus simple possible pour aussi longtemps que possible. Deviens générique si nécessaire, et tu te détesteras par la suite 🙂

Q : comment déprécier un Knob?

Titus Winters : mon truc est de l'ignorer pendant quelques mois. Si personne ne se plaint, tu l'enlèves, et personne n'en aura souffert 🙂

C'était pas mal. Ce fut une bonne journée côté contenu aujourd'hui.

Jour 2 26 octobre

J'arrive avec une quinzaine de minutes de retard car il me faut gérer le démarrage du matin pour les enfants et les animaux; il se trouve que les événements de soirée à CppCon ne sont pas diffusés « live » car ce serait un peu tard les gens sur la côte Est (dont moi), mais sont diffusés le lendemain matin (à 8 h pour nous dans la région de Montréal) et l'autobus de mon plus jeune passe le cueillir, justement, vers 8 h.

Tout de même, après un petit imbroglio pour trouver le lieu virtuel (certaines indications sont erronnées, mais c'est compréhensible étant donné la complexité de la situation), je me retrouve à la première « présentation » de la journée, soit le Committee Fireside Chat de la veille.

Vidéo : https://youtu.be/plQMssXeCpc

Committee Fireside Chat

J'ai manqué le début, alors un survol des points saillants :

Intéressant, même si je savais pas mal tout ça par la force des choses (ça fait encore drôle de dire que ces gens sont des amis, même après plusieurs années). Ils ont un peu dépassé le temps alloué alors l'enregistrement a coupé brusquement, mais l'essentiel était dit et les réponses étaient franches.

Je me dirige vers le premier de mes deux principaux dossiers techniques aujourd'hui, soit une présentation d'un très important mécanisme pour C++ : les exécuteurs, expliqués par le très brillant Eric Niebler par-dessus le marché. Mon autre gros dossier sera l'étude en profondeur de std::vector<T> par David Stone plus tard aujourd'hui. Les exécuteurs sont un des gros morceaux de C++ 23 (si on réussit à les faire accepter pour ce standard; on aurait aimé les avoir pour C++ 20, mais c'est vraiment un truc majeur).

Vidéo : à venir

Eric Niebler – Working with Asynchrony Generically: A Tour of C++ Executors (part 1 of 2)

Eric Niebler se présente, et décrit son intérêt pour l'asynchronisme, en particulier pour la définition d'un modèle cohérent pour la représenter.

Note : je ne noterai pas tout car je veux porter attention à ce qui sera expliqué. La proposition est https://wg21.link/p2300 qui présente un mécanisme pour lancer parresseusement des calculs (sender), accueillir le résultat (receiver) et organiser le tout (scheduler). Un quatrième bloc, le operation_state, complète le portrait

Eric Niebler explique que le but général est d'offrir un cadre conceptuel asynchrone à la hauteur de que d'Alexander Stepanov a fait pour la STL; offrir des abstractions fécondes; iteropérer avec les coroutines; et permettre de savoir où, quand et comment une unité d'exécution s'exécutera.

Eric Niebler présente un (joli!) exemple de répartition de calcul dans un Thread Pool, sans la moindre allocation dynamique de mémoire.

Eric Niebler montre qu'un sender est un Expression Template. Son when_all(Task...) crée un arbre (avec sizeof...(Task) branches!) de tâches susceptible d'être exécuté (et d'entraîner l'exécution des branches)

Eric Niebler présente un exemple de transfert de calcul : les demandes de calculs lourds sont acceptées sur un fil d'exécution à basse latence, puis sont transférés vers un Worker Thread pour être réalisés.

Le design des Sender Adaptors est présenté; chacun prend en paramètre un sender et retourne un sender. Ce sont des transformations, en pratique

Un receiver peut se compléter normalement (set_value()), par une erreur (set_error()) ou par une annulation (set_done())

Eric Niebler poursuit avec une description (animée) du fonctionnement de tout cela. Mieux vaut regarder le tout quand vous le pourrez :) Le modèle des senders et des receivers est un modèle de poupées Russes, mais c'est très général. Eric Niebler montre l'algorithme then à titre d'exemple; la mécanique ressemble beaucoup à mon implémentation C++ 11 de f_g_x(F,G) -> f_g_x_impl<F,G>, pour le sender comme pour le receiver

Note intéressante : tout type awaitable est un sender et peut être passé à un algorithme qui exige un sender. De la même manière, dans une coroutine, on peut faire co_await sur un sender. Eric Niebler montre comment l'annulation d'une tâche s'inscrit dans le processus de co_await sur un sender, car les coroutines n'ont pas de canal d'annulation : ça se comporte comme si une exception « impossible à attraper » (même avec catch(...)) se produisait, mais vu qu'on est dans une coroutine, où les Coroutine Frames sont (conceptuellement, pas nécessairement en pratique) alloués dynamiquement. On « attrape » cette « exception » en appliquant un Sender Adaptor qui représente la situation par une erreur ou un optional<T>. En pratique, on en arrive à des while(true) contenant des co_await qui ne sont pas des boucles infinies (il va falloir que j'investigue pour comprendre quand et où les ressources sont libérées)

Eric Niebler recommande d'utiliser des sender, ce qui donne le choix au code client d'utiliser (ou pas) des coroutines

Q : on parle d'exécuteurs pour le GPU ou pour des systèmes hétérogènes; comment la compilation croisée fonctionnera-t-elle?

Eric Niebler : le contexte d'exécution sera livré par un vendeur (p. ex. : NVIDIA, où je travaille) et il faudra utiliser leur compilateur. Les algorithmes parallèles accepteront des instances de scheduler.

Q : comment déboguera-t-on cela?

Eric Niebler : déboguer du code asynchrone est difficile. Les exécuteurs n'aident pas mais ne nuisent pas non plus.

Q : quand cela sera-t-il standardisé?

Eric Niebler : j'ai confiance pour C++ 23

Q : est-ce un modèle pertinent pour les systèmes répartis?

Eric Niebler : tout à fait

Q : ceci permettra-t-il enfin l'adoption du Networking TS?

Eric Niebler : ces deux efforts ont été découplés et progressent désormais séparément. On aimerait les unifier éventuellement

Q : ceci remplace-t-il les futures et std::async()?

Eric Niebler : non. On peut entre autres écrire un sender nommé as_future qui retourne une future. On n'encourage pas les gens à continuer à utiliser des future une fois ce modèle accepté, mais les deux peuvent continuer à exister. On aimerait adapter std::async() pour qu'elle accepte un scheduler

Brève pause, puis on poursuit... Temps de se prendre un café, mais faut faire vite!

Vidéo : à venir

Eric Niebler – Working with Asynchrony Generically: A Tour of C++ Executors (part 2 of 2)

Eric Niebler poursuit son exposé. Dans cette partie, il discutera d'annulation de tâches et de concurrence structurée.

Eric Niebler explique que par « concurrence structurée », il entend un passage semblable à celui préconisé par la programmation structurée dans les années '60 (mettant les goto de côté), pour faciliter le raisonnement sur la base du code source. Une meilleure composabilité est l'objectif visé

Eric Niebler dit que les modèles traditionnels de concurrence sont de type Fire and Forget, et trace une corrélation avec les goto, alors que les coroutines ont un seul point d'entrée et un seul point de sortie, ce qui les rend composables (et permet de préserver RAII!). Ceci permet aussi raisonnable le passage de paramètres par référence. sans avoir à se lancer dans une danse d'allocation dynamique de ressources.

Les sender / receiver forment aussi un modèle de concurrence structurée : il n'y a pas de calculs détachés possibles. En particulier, si dans une branche d'un when_all une opération est annulée ou échoue, l'arbre entier est annulé ou échoue. Un support intégré pour l'annulation collaborative est fondamental pour atteindre les seuils de « performance » souhaités (ils réutilisent les stop_token de C++ 20; les stop_source sont internes aux algorithmes). Certains algorithmes (p. ex. : stop_when) dépendent carrément de ce mécanisme

Eric Niebler poursuit avec un exemple complexe (sender / receiver / ranges / coroutines). Faudra voir la démo, mais c'est un exemple de simulation de clavier des années '80 :) C'est une présentation détaillée et technique en cinq étapes. On note au passage que l'annulation est subtile (il y a une petite condition de course dans son exemple).

Intéressant, mais dense. C'était bien de voir le code en action, mais je vais vouloir jouer un peu avec le modèle pour me l'approprier (ce qu'on a vu aurait pu être fait avec des Observateurs tout ce qu'il y a de plus traditionnel). La difficulté avec les exécuteurs est le modèle de l'automate qu'ils modélisent, qu'il faut apprivoiser (c'est un peu comme pour les coroutines, en fait; beaucoup de petits morceaux personnalisables).

Je me fais chauffer un bol de quelque chose rapidement, et j'arrive avec une minute de retard.

Vidéo : https://youtu.be/raB_289NxBk

Herb Sutter – Extending and Simplifying C++: Thoughts on pattern Matching using `is` and `as`

Herb Sutter positionne C++ 20 dans une perspective historique : c'est la première fois que le langage rejoint l'essence de son intention originale. Dans les nouveaux chemins à parcourir, on trouve en particulier le Pattern Matching.

Herb Sutter commence par expliquer le rôle historique de switch en comparaison avec if, sur le plan technique (il montre le code généré avec LLVM, qui bascule à une Jump Table une fois qu'il y a au moins quatre cas, default inclus s'il y a lieu) mais surtout sur le plan de l'expressivité, puis passe à inspect, qui est une expression, robuste et générale

Herb Sutter explique pourquoi on vise inspect { patt => { ... } patt => { ... } } plutôt que switch (mieux exprimer l'intention)

Herb Sutter présente son idée de is et as, où X is C est un prédicat (X est un type ou une valeur, C est une contrainte) et x as T qui serait un transtypage généralisé, qui fonctionnerait aussi avec variant, any, etc.

Herb Sutter situe les diverses formes de tests et de conversions dans le langage (il inclut les traits, les tests pour savoir si une future<T> est ready, savoir si un optional<T> est non-vide, le protocole std::get<N>(), etc.). Il suggère de remplacer tous les transtypages et tous les prédicats sécuritaires par des is et des as

Herb Sutter invite Sean Baxter (qui a fait le compilateur Circle, un outil intéressant de métaprogrammation), qui a implémenté cette proposition. La syntaxe est très féconde, et se comporte comme un if constexpr quand le test peut être fait à la compilation. On voit entre autres la force des concepts pour protéger le code contre des aberrations

Herb Sutter montre ensuite que is peut être utilisé dans des clauses requires, mais surtout que c'est utilisable de manière universelle pour tous les types, fondamentaux ou non (il en profite pour faire un plaidoyer pour UFCS, qui va dans la même direction, du moins pour les non-opérateurs)

Herb Sutter montre que is et as sont des opérateurs surchargeables pour divers types. La surcharge pour le I-ième élément d'un variant<Ts...> où sizeof...(Ts)==N fait un requires pour s'assurer que 0<=I&&I<N

Herb Sutter revient avec Sean Baxter et fait une démo avec des expressions inspect, utilisant is pour valider le type effectif associé à un objet générique. Avec any, optional et variant, le test est dynamique, mais le test est le même dans tous les cas

Herb Sutter enchaîne avec inspect combiné à is pour montrer un équivalent naturel de [[fallthrough]] dans un switch

Herb Sutter et Sean Baxter montrent des clauses is imbriquées (p. ex. : inspect (x) { is integral { i is int =>  { ... } is _ => { ... } })

Herb Sutter aborde en fin de présentation deux trucs qui doivent partir de C++ : (a) C++ est trop Unsafe, et (b) C++ est trop compliqué. Plusieurs propositions au fil des ans ont pris ces positions comme base conceptuelle. Souvent, ces propositions brisent (parfois de manière importante) le code existant. On a pensé donner un grand coup avec les modules, mais ça aurait pu causer d'importantes fractures. Il propose de reformuler « trop Unsafe » par « trop facile de causer des bogues » et, en ce sens, il pense que simplifier les transtypages corrects est un gain net. Il propose de reformuler « trop compliqué » par « trop difficile d'exprimer clairement l'intention » et, ici encore, pense avoir mis le doigt sur quelque chose.

Herb Sutter rappelle que is et as ne supportent que les cas sécuritaires et réguliers (on peut encore utiliser reinterpret_cast, mais ces opérateurs ne vont pas là)

Herb Sutter mentionne operator<=> au passage, et dit que le texte du standard en a été réduit de dix pages. Il s'en dit fier :)

Q : est-ce que inspect fonctionne avec des Parameter Packs

Herb Sutter : Sean (Baxter)? (Sean Baxter dit qu'on peut essayer, mais c'était pas dans la proposition de Herb Sutter; Sean Baxter dit qu'on peut étendre un Pack entre les [] pour voir si un is ou un as s'applique à tous les membres)

Q : qu'est-ce qui nous empêche d'avoir ça demain matin?

Herb Sutter : plusieurs choses. Une syntaxe pour répéter les Patterns en profondeur (je pense avoir quelque chose). C'est difficile! De manière générale, ce problème est ardu, et il y a du travail à faire. Le plan de Bjarne Stroustrup a pris 40 ans à prendre forme!

Q : Andrei Alexandrescu dit qu'il voit inspect et void try ... catch. Ça lui semble lourd sur le plan syntaxique. Il pense que ça encourage l'écriture de code différent pour des Patterns différents, donc des branches très disjointes. Il craint pour l'échelonnabilité du mécanisme, et dit qu'il préférerait quelque chose de plus léger. Il précise : il est difficile avec inspect ou avec if constexpr de maintenir des états hors du bloc (étant donné le caractère disjoint des branches)

Herb Sutter : ce n'est pas une panacée. Est-ce que les langages comme Swift et C# qui ont une forme de Pattern Matching te semblent incorrects? Il faudra l'utiliser et développer une idiomatique.

Q : je suis confus par is int pour inspecter une valeur en comparaison avec is int pour un optional<int>

Herb Sutter : pour variant<Ts...>, la réponse est simple, c'est un test dynamique de type. Pour optional<T>, on a un peu comme un variant<T,nullopt>, mais aussi une syntaxe de pointeur, ce qui donne un design un peu confus, mais je vois optional<T> comme un « T ou nullopt »

Q : ... donc si je veux tester le type statique de quelque chose, dois-je utiliser des mécanismes traditionnels?

Herb Sutter : non, je ne pense pas

Q : Jason Turner craint que la gestion de Narrowing Conversions ne devienne facile

Herb Sutter : on ne fait pas de Narrowing Conversions si elles ne sont pas explicites. L'opérateur as est un cast (il convient qu'il nous manque peut-être un mot)

Q : je comprends que as a un profil de performance qui varie selon la conversion, et peut faire un dynamic_cast, or dans notre compagnie nous n'utilisons pas les exceptions

Herb Sutter : préférez is alors

Q : quand Visual Studio inclura-t-il Circle?

Herb Sutter : (on rit) on n'a pas de plan pour ça :)

Q : je pense qu'on ne comprend pas bien les plaintes quant à la sécurité, mais au sens de ce qui se produit quand on fait une bêtise, pas au sens reformulé ici. Je ne pense pas non plus que le langage sera simplifié en ajoutant de mécanismes. Même operator<=> ne l'a pas simplifié (ça a réduit la spécification du standard). Je ne pense pas qu'on aborde le coeur du problème. En Swift, on a beaucoup gagné en disant non à des propositions

Herb Sutter : merci Dave

Q : Bjarne Stroustrup dit que is et as sont des Direct Expressions. Les programmeuses et les programmeurs C++ aiment savoir combien coûtera une instruction ou ce qui se passera. Il aime l'uniformité, et souhaite le Pattern Matching, surtout si c'est utilisable avec un peu n'importe quoi; en ce sens, la proposition de Herb Sutter va au-delà de ses attentes. Bjarne Stroustrup rappelle la syntaxe des concepts comme adjectifs, la complexité sur les Dynamic Bindings, les interfaces variées pour divers types vocabulaires, etc. alors la complexité existe, est possible, mais si on peut offrir des mécanismes plus simples, tant mieux

Herb Sutter : merci

Fort intéressant tout ça, mais ça a fini tard. Je me déplace vers la prochaine salle de présentation, où je pique une jasette avec le toujours sympathique Gabriel Aubut-Lussier.

La présentation de David Stone qui débute est la première cette semaine (pour moi du moins) qui ne se fait pas par Zoom mais bien de manière embarquée dans le fureteur.

Vidéo : à venir

David Stone – Implementing static_vector: How Hard Could it Be?

David Stone dit que c'est pas impossible :)

David Stone : on parle de static_vector<T,Capacity>. Plus petit sur la pile, pas d'indirection pour accéder aux éléments (toujours en Cache!). Toutes les fonctions importantes sont constexpr et size() est static. Le type utilisé pour la taille dépend de la valeur de Capacity

Pour représenter l'espace d'entreposage, il a pensé à utiliser un stack_allocator sur un array<std::byte,Capacity*sizeof(T)>, mais ça l'aurait empêché d'utiliser static_vector<T,Capacity> comme un type valeur, et le layout en mémoire n'aurait pas été aussi avantageux. Il a pensé utiliser une paire array<T,Capacity> et un size_t (ou équivalent pour le nombre d'éléments) mais ça aurait construit les T. Il a regardé du côté de array<std::byte,Capacity*sizeof(T)> plus taille, mais ça ne respecte pas l'alignement de T... Il s'est construit un uninitialized_array<T,Capacity> avec un std::aligned_storage sous la couverture, mais ça implique des reinterpret_cast et on ne veut pas perdre constexpr. Il a ajouté un union pour choisir entre array<T,Cap> et aligned_storage<T,...> selon la trivialité de T. Ça nous rapproche du bonheur, malgré l'imperfection; c'est ce qu'il a réussi à trouver de mieux

Q (Ben Deane) : il y a un [[no_unique_address]] sur l'attribut qui représente l'entreposage, pourquoi?

David Stone : parce qu'il se peut que Capacity soit zéro :)

David Stone procède avec le descriptif des méthodes (il opère sur de la mémoire brute). Il fait remarquer que construct_at() est constexpr mais Placement New ne l'est pas. Son test de if(full()) grow(); est remplacé par un assert(!full());. Il fait remarquer que les algorithmes de std::uninitialized_* ne sont pas constexpr car ils sont spécifiés pour utiliser Placement New, alors il faudrait les corriger. À propos de memcpy(), il y a une subtilité : les pointeurs ne peuvent être nuls même si la taille est zéro (David Stone rappelle que nullptr+0 est permis en C++ mais interdit en C). Son mouvement utilise un uninitialized_relocate(), et est conditionnellement trivial. Son swap permute les éléments un à un (complexité linéaire), alors on constate une différence avec vector<T>.

David Stone termine avec quelques améliorations que ceci suggère pour le standard : rendre std::uninitialized_* constexpr, adapter std::memcpy() pour supporter nullptr+0, rendre uninitialized_array possible, et adopter trivially_relocatable.

Plusieurs questions intéressantes mais techniques ont suivi. Beau travail de la part de David Stone... qui était dans la salle virtuelle car c'était une rediffusion de sa présentation d'hier soir. Toutefois, la prochaine (vers laquelle je me dirige) se tient « live ».

Vidéo : à venir

David Stone – Faster, Easier, Simpler Vectors

David Stone commence avec une diapo qui présente une courbe de croissance quadratique, mais ne donne (délibérément) pas la légende

David Stone dit qu'il existe plusieurs sortes de vector, avec diverses caractéristiques, et qu'il veut s'intéresser à ce qui permet de les écrire... ou d'écrire des programmes en général... ou de penser un langage de programmation

David Stone explique qu'il porte son chapeau de Vice-Chair de EWG pour cette présentation. Il met l'accent sur les caractéristiques de « performance » qui sont au coeur de ses préoccupations

David Stone : mon approche au design logiciel est de débuter par un objectif. Je pense mes interfaces ensuite (au sens large du terme, incluant les garanties de complexité). David Stone présente quelques types de tableaux (array, uninitialized_array, uninitialized_dynamic_array, dynamic_array, etc.), puis quelques types de vector (std::vector, 32 bits vector qui peut être plus petit -- et plus rapide -- pour certaines opérations, static_vector, small_buffer_optimized_vector, stable_vector -- qui ne fonctionne que sur certains systèmes, et alloue toujours une très grande capacité ce qui évite toute relocalisation, quitte à utiliser de la mémoire virtuelle -- ou encore small_stack_footprint vector qui occupe peu d'espace quitte à itérer moins vite)

David Stone : une interface minimale et complète pour vector, permettant d'appliquer des algorithmes dessus, inclut un constructeur par défaut, un constructeur de séquence, un constructeur de copie, un constructeur de mouvement, affectation de copie, affectation de mouvement, destructeur, begin(), end() ou size() au choix, capacity(), append_from_capacity() (une nouveauté, bas niveau mais qui permet d'écrire des abstractions de plus haut niveau et d'écrire un append() efficace), reserve(), et operator[] (requis en C++ car ça doit être une fonction membre)

David Stone : cela permet d'implémenter tous les autres efficacement

David Stone mentionne que initializer_list<T> est un type référence et qu'on ne peut pas en déplacer les éléments (ça impose des copies), ce qui peut nous ralentir. Aussi, ce type complique l'écriture du code générique. Il propose un template alias c_array<T,source_size> avec un cas particulier qui correspond à empty_c_array_parameter (classe vide) qui permet de contourner ces irritants.

David Stone aborde la question des allocateurs. Ils occupent de l'espace; de plus, avec un stack_allocator, un vector alloue à travers une indirection. Plutôt que de permettre de paramétrer les conteneurs, il suggère de simplifier les conteneurs. Il propose de combiner réutilisabilité, généricité et (il m'en manque un) pour obtenir la simplicité

David Stone présente son implémentation de vector sur la base de l'implémentation minimale. Il se trouve que les autres opérations sont implémentées sous la forme de fonctions non-membres, avec des tests de mécanismes supportés à travers if constexpr. Il montre un truc (lazy_pushback est un exemple) où on utilise un foncteur de relais en tant qu'optimisation. Je passe les détails (je les connais bien pour l'essentiel) mais c'est intéressant et ça vaut la peine d'être vu. En réponse à une question sur realloc(), il explique qu'un nouveau service des allocateurs en C++ 23 permet de faire mieux que realloc() en pratique

David Stone démontre le code généré pour un vector standard et pour l'un de ceux qu'il propose. Il y a un bon gain dans le code généré (surtout de par le recours réduit aux indirections)

David Stone se questionne ensuite sur le design de nos propres types : quel est le chemin suivi? Déterminer un cas d'utilisation (dans son cas, std::vector<T> ne convenait pas); définir une interface minimale et efficace pour le concept représenté; examiner des problèmes semblables pour voir si cette interface minimale s'y transpose bien; voir si on parvient à exprimer efficacement les solutions aux problèmes visés (dans son cas, append() est plus efficace que insert(end(), ...)), enrichir de l'extérieur.

Q : existe-t-il une suite de benchmarks bien établie pour de tels types?

David Stone : je n'en connais pas personnellement, mais avoir une interface minimale réduit l'espace à tester. Pour mes fins, j'ai pris les suites de test d'une bibliothèque standard. Notez que le code généré seul n'est pas une métrique suffisante : au début, je battais std::vector par une bonne marge, disons 5%, sur de petits échantillons, mais je pouvais perdre par un facteur de 30% avec des millions d'éléments.

David Stone : avec mon approche, on finit par une bonne quantité de fonctions non-membres. L'opérateur |> proposé pour C++ 23 pourrait aider.

David Stone : il y a un peu d'introspection à faire. Outre ces fonctions qui compliquent un peu la lisibilité, on aimerait que les ajustements à la représentation aient moins de conséquences. Plusieurs opérations sont un peu plates à implémenter (constructeur par défaut; begin() avec ses surcharges const et non-const -- deducing this va aider -- ou encore le fait que operator[] doive être une fonction membre, swap() qui est un peu Boilerplate-ish, etc.).

Intéressant et solide sur le plan technique. Il y a beaucoup de bonnes idées dans le code de David Stone. En sortant, je salue Jérémi Panneton, à qui j'ai enseigné il y a plusieurs années maintenant et que je suis bien content de voir ici (j'ai aussi vu Guillaume Matte et Pier-Antoine Giguère à qui je n'ai pas eu le temps de parler).

Il me reste la présentation de mon ami Michael Wong et de ses deux experts-collègues sur les prochains ajouts à la Concurrency TS...

Vidéo : à venir

Paul E. McKenney, Maged Michael et Michael Wong – The Upcoming Concurrency TS Version 2 for Low-Latency and Lockless Synchronization

Michael Wong présente d'abord ce que contenait la Concurrency TS version 1. Une grande partie a été intégrée à C++ 17 ou à C++ 20. Certains aspects, très puissants et se prêtant à la synchronisation sans verrou, ont été reportés à plus tard.

Michael Wong explique que certains trucs sont longs parce que la vie interfère (changement d'emploi, famille, ce genre de truc), et d'autres pour des raisons techniques (ce qui est dans une TS tend à passer après ce qui va dans un IS; il fallait adapter certains idiomes de C à C++). Il explique ensuite pourquoi on utilise des TS (et pour quelles choses on ne les utilise pas), et comment on fait la transition de TS à IS en temps et lieu.

Michael Wong explique que les deux plus gros morceaux de cette TS sont RCU et les Hazard Pointers. On s'attend à ajouter des compteurs concurrents, des files concurrentes, des clôtures asymétriques, etc.

Maged Michael aborde la question des Hazard Pointers en vue de C++ 26. Un Hazard Pointer est SRMW pour lequel un test (pointe-t-il sur un objet) doit être fait avant réclamation. Un Hazard Pointer s'accompagne d'un domaine (qui repose sur un pmr::polymorphic_allocator<byte>) pour gérer les pointeurs et les objets réclamés; il existe un domaine global pour les gens qui ne veulent pas s'en préoccuper. Un Hazard Pointer pointe sur un hazard_ptr_object_base<T,D=default_delete<T>> qui expose une fonction membre retire(); le hazard_pointer lui-même a des fonctions (protect, try_protect, reset_protection) pour gérer la réclamation et la protection contre la réclamation.

Maged Michael discute ensuite des progrès dans l'implémentation des Hazard Pointers au fil des ans, et de leur utilisation chez Facebook. Il poursuit en relatant l'ensemble minimal de fonctionnalités (p. ex. : réclamation synchrone) à faire adopter pour C++ 26. Plusieurs questions suivent sur la mécanique de réclamation (ça coûte cher, alors ils attendent d'avoir environ 40K pointeurs à réclamer, pour amortir le coût par pointeur).

Paul E. McKenney aborde quant à lui la question de RCU. Il relate son expérience d'apprentissage à l'écriture de RCU en C++ à partir de son expérience en C. Il a fait rire de lui en utilisant des méthodes virtuelles au début, puis a joué avec CRTP (avec tout l'étonnement qu'on peut imaginer). Pour lui, ce fut un choc d'acclimatation à C++... et pour ses collègues, un cho d'acclimatation à RCU.

Paul E. McKenney présente quelques ajustements qui sont survenus sur son design initial... dont certains qui sont revenus vers le noyau de Linux! Il parle entre autres des différences entre le noyau de Linux et les applications en C++ (qui peuvent se permettre de planter puis de redémarrer... parfois!). Il présente des évolutions de sa vision de l'utilisation de RCU en C++ avec des exemples pas si mal. Il montre des exemples pour lesquels RAII ne sied pas tout à fait.

Paul E. McKenney discute de quelques enjeux et risques avec RCU en C++. Aussi, RCU pourrait bénéficier de memory_order_consume... Selon lui, rie de tout cela n'empêche l'adoption de RCU avec C++ 26.

Q : je comprends que RCU fonctionne dans le noyau car on ne se fait jamais interrompre. Et dans le code client?

Paul E. McKenney : faut savoir si on est dans un rcu_reader ou pas, en fait. Le vrai requis est de ne pas traîner dans un rcu_reader.

Pas mal considérant la complexité du sujet. C'est le genre de truc (les Hazard Pointers et RCU) qui sont pas mal à la point de la complexité en termes de gestion de la concurrence.

Jour 3 27 octobre

J'arrive avec quelques minutes de retard pour cause de gestion de démarrage de la journée. La rediffusion des Lightning Talks de la veille est démarrée depuis peu alors j'en ai peut-être manqué un...

Vidéo : à venir

Lightning Talks

En gros, les Lightining Talks de ce matin sont :

C'est un peu plus court qu'à l'habitude (à cause de la répartition des gens sur place et en ligne je suppose; en personne, les Lightning Talks, c'est vraiment sympathique!), mais j'ai de la correction urgente à faire alors je prends la trentaine de minutes gagnée pour finir les trucs les plus pressants.

Par la suite, je me dirige vers la salle où Vassil Vassilev présente son utilisation de l'interpréteur Cling, un chouette outil, pour l'avancement des sciences.

Vidéo : à venir

Vassil Vassilev – Interactive C++ for Data Science

Vassil Vassilev explique qu'il va nous parler de ce qu'est C++ en mode interactif, et ajoutera divers exemples d'application dans le domaine scientifique. Il annonce plusieurs trucs intéressants (instanciation de templates sur demande; traitement des erreurs; Undo; interopérabilité; etc.)

Il présente un exemple avec OpenGL où il modifie la couleur d'une forme en rotation « live ». Il en profite pour mettre en valeur l'intérêt d'un cycle de développement plus court avec une rétroaction plus immédiate.

(je manque quelques minutes dû à une urgence familiale; à mon retour, Vassil Vassilev parle de ce que supporte Cling)

Avant de présenter le rôle de C++ en mode interactif dans les Data Sciences, Vassil Vassilev commence par décrire ce domaine, qu'il place comme un des quatre piliers des sciences avec la science empirique, la science théorique et (il m'en manque un). Il situe Cling dans le contexte de Jupyter et de ses Notebooks, ce qui permet de profiter des technologies Web pour présenter des images, accéder à l'aide en ligne, connecter des glissières à des variables, etc.

Un collègue que Vassil Vassilev fait ensuite une démonstration qui utilise CUDA pour exécuter le Game of Life de Conway. Cela permet de mettre en valeur des subtilités (p. ex. : quand une partie des états de la simulation sont sur le GPU, on n'a pas le même contrôle)

Vassil Vassilev décrit les atouts de l'instanciation de templates sur demande, et parle d'interopérabilité avec Python. Il mentionne que certains mécanismes comme le Reference Counting sont plus difficiles à simuler. Il y a des coûts importants à l'interopérabilité avec Python, où tout se fait à l'exécution, mais c'est utile dans plusieurs domaines. Il présente aussi SIL, qui sert pour l'interaction entre C++ dans Cling et du code D, de même que Cxx.jl pour interopérer avec Julia.

Vassil Vassilev explique ensuite que Cling, comme Clang, a été pensé comme une bibliothèque, et peut servir à titre de Compiler as as Service. Il donne un petit exemple où il compile dynamiquement le texte d'une fonction calculant le cube d'un entier, puis prend son adresse et l'appelle.

Vassil Vassilev explique comment certaines optimisations peuvent être mieux faites une fois la première compilation réalisée, et montre les étapes d'optimisation possibles avec Cling.

Vassil Vassilev présente un exemple amusant où un programme C compile dynamiquement un template C++ puis l'utilise :)

En fin de présentation, Vassil Vassilev explique que C++ demeure un langage compilé, mais peut être mis entre les mains de scientifiques qui ne sont pas des experts programmeurs.

Q : peut-on contrôler les fanions d'optimisation du Compiler as a Service?

Vassil Vassilev : oui, tout à fait, mais il faut faire attention car le temps requis pour compiler et optimiser une fonction peut impacter négativement le temps d'exécution interactif.

Q : est-ce un bon outil pour apprivoiser C++ dans un contexte pédagogique?

Vassil Vassilev : oui. Une université Française nous a indiqué l'avoir essayé et avoir beaucoup apprécié l'expérience

Q : est-ce que les fournisseurs de services infonuagiques offrent ceci?

Vassil Vassilev : pas encore à ma connaissance

Q : pourrait-on utiliser l'optimisation dynamique pour identifier les cas pertinents de [[likely]]

Vassil Vassilev : je pense que c'est effectivement ce que font les optimisations par profil

C'était intéressant, bien que peu dynamique (le présentateur avait pré-enregistré sa présentation; cela dit, il était là pour répondre au questions du public).

C'est mon « matin interprété » ce matin, avec une présentation sur le C++ interactif, suivie d'une discussion sur des types « à la Python » juste après (Kris Jusiak est un programmeur amusant, avec plusieurs tours dans son sac).

Vidéo : à venir

Kris Jusiak – ++namedtuple - Python-style Named Tuples in C++

Kris Jusiak va nous montrer ses trucs, et veut nous vendre l'idée que ces uplets compilent plus rapidement que std::tuple :)

Kris Jusiak commence par expliquer la nature des uplets de Python et la motivation derrière ce mécanisme. Ensuite, il présente les irritants avec std::tuple : un tuple<int,int> ne permet pas de faire get<int>(tup); passer par les indices ou des Structured Bindings peut donner les mauvaises valeurs si on se trompe; les valeurs respectent l'alignement, donc ne sont pas Packed et pourraient occuper moins d'espace en mémoire...

Kris Jusiak présente son « rêve » d'un constexpr auto tup = tuple(x = 42, y = 3); dont on pourrait évaluer le type avec decltype, transformer en JSON, afficher pour obtenir des paires {nom,valeur}, etc. et même utiliser SFINAE (p. ex. : template <class Tup> requires(t.x) ...) ou permettre la modification par nom (p. ex. : tup.x++) ou étendre un uplet en ajoutant des membres

Kris Jusiak poursuit avec la réalité. Avec des UDL pour les noms de membres, il est arrivé à quelque chose de pas mal (p. ex. : "x"_t = 42 au lieu de x = 42; tup["x"_t] au lieu de tup.x). Le support de get<N> et de Structured Bindings est là mais perfectible. L'extension, l'impression,SFINAE, tout cela fonctionne et est constexpr

Kris Jusiak parle ensuite de Row Polymorphism, que Haskell supporte et que son type supporte aussi.

Q : quel genre d'erreur à la compilation as-tu quand tu te trompes dans un nom?

Kris Jusiak : la plupart du temps, Constraint not satisfied car ça repose sur des concepts

Kris Jusiak poursuit avec une esquisse du code (approximativement 100 lignes de code). Les faits saillants :

Son "nom"_t est convertible et comparable à un std::string_view. Il est passé par un fixed_string de https://wg21.link/P0732 pour contourner des limites du langage (pour ne pas avoir à gérer des template<char...>). La diapo 27 donne le code (c'est intéressant)

Son namedtuple<Ts...> dérive de Ts... car chaque élément du Pack est un nom, donc un type unique. Ça lui donne le get<fixed_string,Tup>(Tup) gratuit : il suffit de se retourner soi-même et la conversion vers le bon parent fait le travail (chouette!). Les valeurs associées aux noms sont des any

Note : il utilise une valeur de retour tardive (auto f() -> T) pour bénéficier de SFINAE sur T

Son extends<Tup, fixed_string... Names> est très joli : (requires(T t, args<Names> arg) && ...)

Pour avoir les Structured Bindings, de même que get<N>, ça ne fonctionne pas encore (les deux vont ensemble; get<N> permet les Structured Bindings). Pour y arriver, il implémenter tuple_size<namedtuple<Ts...>> et tuple_element<N, namedtuple<Ts...>>. Son get est bref mais utilise une IIFE à l'interne. Ça lui permet d'implémenter l'affichage avec std::apply au passage

Pour obtenir le Packing, Kris Jusiak trie les Ts... par taille :)

https://godbolt.org/z/4889PqPGb pour le code :)

Kris Jusiak présente des cas d'utilisation, par exemple un vector<decltype(employee)> dans lequel il fait des emplace_back(), où employee est namedtuple<"Employee">("name"_t,"age"_t,"title"_t)

Kris Jusiak montre un to_json() et suggère un from_json(), qu'il laisse en « exercice pour le lectorat ». C'est chouette :) Il en profite pour faire un plaidoyer pour la réflexivité statique, qui aurait rendu le tout un peu plus simple

Kris Jusiak aborde la question des Benchmarks comparatifs en termes de temps de compilation et de vitesse d'exécution. Le choix du compilateur et le choix de bibliothèque standard semblent avoir de l'influence...

Q : quels sont les bénéfices à utiliser un namedtuple plutôt qu'un struct?

Kris Jusiak : ça nous donne les noms, évidemment. Ça nous donne aussi le Packing

Q : les affichages sont-ils triés par nom?

Kris Jusiak : la sortie sera triée par taille dû au Packing. Ça ne change rien pour JSON, mais ça peut jouer des tours pour la stabilité des sorties

Q : peut-on faire un namedtuple contraint pour ne pas être pris avec des any?

Kris Jusiak : peut-être, mais je ne suis pas sûr de bien comprendre la question...

Beaucoup de bonnes idées, peut-être même applicables au type contexte de mon doctorat (à évaluer). Tout ce qui peut nous sauver temps et espace... Il y a un enjeu de support par le compilateur quand le nombre de membres devient grand, et le côté plus dynamique des noms dans mon contexte (!), mais à investiguer...

La prochaine présentation sera par Lisa Lippincott qui donne toujours beaucoup de stimuli au cerveau... Puisqu'il s'agit d'un Keynote, il y a une publicité... Jon Kalb fait quelques annonces (concours et autres, incluant une poupée Bjarne Stroustrup!), puis présente Lisa Lippincott comme la présentatrice préférée de Tony Van Eerd :)

Vidéo : https://youtu.be/8KwD1Sz6um4

Lisa Lippincott – Value in a Procedural World

Lisa Lippincott commence en parlant de ces projets qui, quand on les débute, sont personnels et amusants, puis attirent l'enthousiasme de tas de gens, évoluent énormément, puis deviennent tels qu'il devient ardu de changer les bases. Elle parle de la manière de parler de five is bigger than two par exemple, où five et two sont des noms qui réfèrent à des choses distinctes. Elle aborde la question de la réalité de ces noms, et parle de l'idéal platonicien : values are real like rocks are real

Lisa Lippincott : comment savons-nous que cinq est plus grand que deux? Selon Platon, les humains « vivaient » dans un monde idéal et platonique avant d'exister sur Terre et auraient « vu » en un certain sens cette réalité

Lisa Lippincott parle de Gottlob Frege, platonicien dans son approche, et de l'introduction des symboles (pas ceux qu'on utilise aujourd'hui) pour « il existe » et « pour tout ». Ceci a permis de passer de « 2+2==4 » à « il existe un telle chose que 2 »

Lisa Lippincott rappelle que Bertrand Russell a un peu brisé le rêve de Gottlob Frege, malgré lui car il était un fan de ses travaux. Georg Cantor a ensuite proposé que les ensembles sont ce qui existe vraiment dans un monde idéal platonicien (5 est un ensemble), en apportant un ensemble de règles pour soutenir sa théorie... mais Bertrand Russell a encore une fois tout détruit bien malgré lui

Lisa Lippincott enchaîne avec Ernst Zermelo, avec une théorie qui essayait d'éviter que Bertrand Russell ne détruise tout, mais Abraham Frankel a démontré que cette théorie n'était pas tout à fait suffisante, puis il a fallu ajouter l'axiome du choix pour obtenir le modèle ZFC des ensembles, qui est le modèle utilisé aujourd'hui. ZFC est une théorie de ce qui existe dans le monde platonicien

Lisa Lippincott poursuit avec le Formalisme, qui est moins mystique que le Platonicisme et ne présume pas un monde idéal précédant le nôtre. Le formalisme, c'est un peu comme ajouter un bonhomme sourire à la fin des phrases pour clarifier qu'on n'y croît pas en tant que chose fondamentale. Elle poursuit avec le Fictionalisme qu'elle illustre par deux mains (le chiffre cinq avec cinq doigts, et la main de Spock). Une bonne histoire peut se raccorder au monde réel, et permet de faire des aller-retours comme ceux entre Ernst Zermelo et Abraham Frankel. On peut les voir comme ayant collaboré à une bonne histoire, à une Parabole of the Sets

Lisa Lippincott : le Fictionalisme n'est pas parfait. On peut se demande « Quel Spock » par exemple, comme on peut se demande « Quel 5 ». Elle ajoute qu'on peut, avec cette théorie, se demander pourquoi une preuve mathématique aurait plus de valeur qu'un autre narratif.

Lisa Lippincott : ces trois théories sont des formes d'Essentialisme : qu'est-ce qu'une valeur? Qu'est cette chose? C'est un problème difficile, alors je vais me tourner vers le Fonctionnalisme : que fait cette chose? Comment interagit-elle avec le monde? Je peux la regarder de l'extérieur et en étudier les effets, réaliser des expériences

Lisa Lippincott : je vais donc traiter les valeurs comme s'il s'agissait de phénomènes naturels et regarder ce qu'elles font, quel est leur effet. Cela amène la question : comment observer une valeur? Constat : certains chemins d'exécution reflètent des valeurs distincts (p. ex. : prendre ou ne pas prendre un if). Ceci permet d'observer non pas une valeur, mais bien une distinction entre deux valeurs, ce qui nous rapproche du Point-Less Topology en Mathématiques. Si on collecte les diverses distinctions d'une même exécution, on obtient un ensemble ce qui permet d'observer la notion de valeur dans ses possibilités

Lisa Lippincott : la question qui suit est What do we observe values doing? Par exemple, faire const bool b = f(); ... if(b) stmt; ... if(b) stmt; ... nous mènera à prendre le même chemin deux fois. Quel est le principe qui explique cela? Je nommerai ce principe Stability.

Lisa Lippincott : faire const bool b = f(); ... if(b) stmt; ... const bool c = b; ... if(c) stmt; ... nous mènera à prendre le même chemin deux fois, mais la valeur c n'existait pas lors du premier branchement alors Stability ne suffit pas à l'expliquer. Ce principe est Substitutability qui reflète le sens d'une copie.

Lisa Lippincott faire const int a = f(); ... const bool b = (a > 2); ... if(b) stmt; ... const bool c = (a > 2); ... if(c) stmt; ... nous mènera à prendre le même chemin deux fois. Cette répétition d'un appel de fonction (mêmes sorties pour les mêmes intrants). Elle exprime 2 comme une opération qui produit un entier... Ce principe est Repeatability of function calls.

Lisa Lippincott : ces trois principes me semblent couvrir tous les comportements observables des valeurs. Stability : same object, different time. Substitutability : different object, same time. Repeatability : different object, different time. Elle traduit ces idées en termes d'interfaces de fonctions et du sens qu'on leur accorde

Lisa Lippincott présente sa notation qui sépare l'interface (où elle accorde des responsabilités à l'appelant) et l'implémentation (où elle accorde des responsabilités à l'appelé). Elle exprime sous notation fonctionnelle l'affectation d'un double à un double. Sa signature est double(double &left,const double right) et elle exprime que l'appelant abandonne sa responsablité pour Stability pour left lors de l'appel, puis la regagne après l'appel. Suivant l'appel, claim substituable(left,right) s'avère

Lisa Lippincott : pour comprendre le concept de Stability, il faut avoir une manière de discriminer deux valeurs (sans le concept de différence, celui de stabilité est fragilisé). L'opérateur == ne suffit pas car il demande de comparer les deux valeurs au même moment, or Stability repose sur un test à deux moments distincts. L'opérateur == pose aussi problème pour zéro (il y en a deux, mais ils sont égaux au sens de ==) et pour NaN (NaN est substituable mais (NaN==Nan)==false). Il nous faut donc un autre concept pour formellement comparer des valeurs

Lisa Lippincott : rappel, des chemins distincts montrent des valeurs distinctes. Quelque chose comme if(n > 2) coupe le monde en deux groupes et je nommerai ceci Distinct meaningful value (elle évitera && ou || pour éviter les court-circuits). Elle montre qu'imbriquer des alternatives permet de montrer un plus grand nombre de distinctions, et qu'il en va de même pour des séquences de if ou des sélectives (avec et sans break). Les répétitives permettent aussi de distinguer des valeurs par un nombre d'itérations. Enfin, il reste un mécanisme, mais elle suggère de l'éviter si possible (ce n'est pas toujours possible en pratique) pour examiner les valeurs; elle utilise l'exemple de int isatty(int fildes) dont l'interface est tellement mal pensée qu'une page Wikipedia lui est dédiée. Le problème est le comportement documenté (une horreur), et peut ne pas donner de réponse raisonnable (il y a un may dans la description!). Cette situation se modélise par une exception, et le catch équivaut à une lacune, une information manquante. Une lacune (un trou dans la connaissance) complique beaucoup le raisonnement

Lisa Lippincott : que des chemins distincts montrent des valeurs distinctes est un outil d'observation. Elle introduit un marqueur nommé discernible_bitwise(const byte&) qui permet de savoir quels bits sont constatés à 1 par une série de tests, puis operator discernible(const byte&). Par la suite, elle examine comment discerner deux vector<T>, et montre que sur la base de la représentation, seuls le nombre d'éléments et les valeurs des éléments participent à la valeur (les pointeurs et la capacité ne le font pas)

Lisa Lippincott : nous avons examiné une relation entre les fonctions et les ensembles de valeurs. L'un peut représenter l'autre, et c'est une relation entre monade et comonade, et on peut dire qu'un ensemble est une fonction. Une valeur est un chemin à travers une fonction; la relation entre valeur et chemin est aussi une relation de représentation équivalente

Lisa Lippincott : quand nous choisissons le nom d'une fonction, nous évitons d'utiliser des noms, préférant en général des verbes : des choses qui se produisent, pas des choses qui sont. J'ai utilisé des adverbes dans certains cas; ça me suggère qu'on pourrait peut-être percevoir une valeur comme une manière selon laquelle un événement peut se produire, une manière selon laquelle une fonction peut être exécutée. The number of fingers is five pourrait s'exprimer The fingers were counted fively...

Q : dans la diapo sur la structure du vector<T>, n'aurait-on pas dû avoir un champ sur la taille?

Lisa Lippincott : exécuter le discern six fois et l'exécuter huit fois encode une distinction visible; au sens qui nous intéresse, c'est une différence observable

Q : peux-tu nous expliquer monade et comonade au sens entendu aujourd'hui?

Lisa Lippincott : pensez une fonction comme un ensemble d'exécutions possibles. Ces distinctions et leur topologie forment une catégorie, et il est possible de réaliser un équivalent homéomorphique avec les valeurs

Q : wow. J'ai mal compris en quoi discern capture mieux la provenance des valeurs que operator==

Lisa Lippincott : c'est subtil, mais l'idée est que ça ne peut pas s'écrire en C++. Il faut que ce soit fourni par l'implémentation, et celle-ci a sa propre vision de la provenance (ça permet des optimisations, et ça permet d'éviter le comportement indéfini dans certains cas)

Q : vous nous offrez encore aujourd'hui un cadre de raisonnement. Ce que je vois ici est le fait que pour les mêmes intrants on obtient les mêmes extrants. Est-ce l'une des raisons pour l'abandon du code auto-modifiant?

Lisa Lippincott : je vois un lien, mais je ne pense pas que la mutabilité empêche le raisonnement. Je pense toutefois qu'elle complique le raisonnement (elle donne l'exemple d'un appel indirect à travers un pointeur de fonction; est-ce que deux appels à travers deux pointeurs distincts sont équivalents?)

Q : comment comprendre les deux foncteurs (au sens de la théorie des catégories) à la fin?

Lisa Lippincott : avec l'espace topologique des exécutions possibles d'une fonction, et l'ensemble des possibilités d'un ensemble, si je prends une fonction et l'amène du côté des ensembles, je vais constater qu'une collection d'exécutions devient une collection de valeurs. Dans l'autre sens, un ensemble mappe vers une fonction un peu de la manière décrite par ma fonction discernable

Q : comment appliquer ceci à la testabilité? Je pense aux tests unitaires et à la couverture de code.

Lisa Lippincott : j'y ai pensé un peu. Je pense que l'idée de la couverture de code rejoint celle de la couverture des valeurs d'un ensemble donné

Q : discernible utilisait des tests avec l'opérateur < mais en quoi est-ce distinct de l'opérateur ==?

Lisa Lippincott : outre les pointeurs, où la discernabilité bit à bit n'est pas ce qu'on veut, on peut supposer une comparaison bit à bit sur des fondamentaux comme des bytes

(je dois quitter pour un truc familial, alors je manque la fin de la très intéressante période de questions)

Je dois faire un aller-retour très rapide pour aller chercher notre chien Crapoutte chez la toiletteuse, puis je reviens comme la présentation de Gabriel Dos Reis débute.

Vidéo : à venir

Gabriel Dos Reis – In-Memory and Persistent Representations of C++

Gabriel Dos Reis explique qu'on essaie de résoudre le problème d'une représentation de C++ autre que celle du texte du code source. Il en existe déjà plusieurs, mais avec l'avènement de modules, on a une occasion de se rapprocher d'une représentation commune

Gabriel Dos Reis : nous avons besoin de deux représentations, soit une en mémoire et une sur disque. En principe, il est avantageux de faire en sorte qu'elles ne divergent pas trop. Dans mon approche, le focus est sur la sémantique, sur l'essence du langage plutôt que sur les irrégularités (on veut le sens, pas la syntaxe). On veut aussi un peu d'espace pour les extensions, pour lesquelles on ne contrôle pas la régularité

Gabriel Dos Reis : en date d'aujourd'hui, le langage comprend 347 non-terminaux, et la liste croît...

Gabriel Dos Reis : en collaboration avec Bjarne Stroustrup, en 2004 lorsque nous étions collègues au Texas, nous avons mis en place une courte série de principes que nous souhaitons réguliers (p. ex. : toute expression a un type). On souhaitait représenter toutes les constructions régulières de C++ hormis les macros avant expansion. On voulait être neutres quant aux compilateurs et avoir une représentation échelonnable. Nous avions choisi de combiner des hiérarchies de classes (nos maths analytiques), les templates pour ce qui est commun entre les types (notre algèbre), séparer interface et implémentation (ingénierie logicielle), et appliquer un peu de bon goût (esthétique). L'implémentation originale utilisait le langage de l'époque et le schéma de conception Visiteur

Gabriel Dos Reis montre schématiquement la représentation (IPR) à l'époque. C'était (volontairement) un langage basé sur les expressions. Le code source est disponible

Gabriel Dos Reis aborde ensuite la question de la représentation persistante. Les principes étaient les mêmes qu'à l'époque. Il relate le passage de MSVC d'une modélisation des templates comme une séquence de jetons à une représentation plus abstraite, et tient ce cas d'espèce comme un rappel de l'importance de l'abstraction

Gabriel Dos Reis parle de l'entreposage de choses comme une vtbl; il est clair que sur disque, la réalité sera différente de ce qu'elle est en mémoire. Il a décidé de suivre les principes de la programmation générique mis de l'avant par Alexander Stepanov et Dave Musser, en particulier le Gradual Lifting. Il donne l'exemple de l'écriture d'un find générique à travers des itérateurs, en comparaison avec une version non-générique. Il dit en arriver avec du Generic Programming for a Bloc of Data :)

Gabriel Dos Reis travaillait déjà sur les modules pour MSVC, alors il connaît la forme du Blob of Data. Il montre un schéma du modèle général, puis aborde les artéfacts de la compilation des modules, qui exposent des interfaces et devraient être indépendantes du compilateur. Selon lui, ça devrait pouvoir servir comme base de travail; le compilateur n'est plus là à ce stade, mais on pourrait faire intervenir toutes sortes d'outils (et on en a besoin!). Il mentionne par exemple de l'introspection dynamique Type-Safe ou des calculs répartis avec marshalling gratuit.

Gabriel Dos Reis a publié le format du IFC de Microsoft public sous licence permissive, sur Github et aujourd'hui même. Il demande aux gens de se l'approprier et de s'en servir! Il poursuit en expliquant ce format (c'est intéressant mais je ne prends pas en note; c'est de la documentation de format binaire). Chose intéressante : dans la représentation IFC il n'y a pas de différence entre paramètre d'un template et paramètre d'une fonction (un paramètre peut être un type ou une variable)

Gabriel Dos Reis rappelle que ce format n'a pas à correspondre à la représentation interne du compilateur (ce n'est même pas le cas chez Microsoft!)

Q : j'implémente des outils pour Clang et naviguer l'AST peut être compliqué (c'est colatile). Qu'en sera-t-il de l'IFC?

Gabriel Dos Reis : tu ne veux pas transformer l'AST en IFC, mais tu peux vouloir le transformer en IPR

Q : nous allons faire des erreurs, or matérialiser la représentation sur disque semble nous imposer un fadreau d'entretien

Gabriel Dos Reis : seulement si tu veux supporter plusieurs versions du langage d'un même outil

Q : Bjarne Stroustrup dit que les craintes des gens tient du fait que les gens confondent simplicité et familiarité. Quand nous avons écrit IPR, les gens disaient que nous ne comprenions pas C++... ils parlaient de Gabriel Dos Reis et moi!

Q : je crains de ne pas comprendre le but...

Gabriel Dos Reis : quand une définition de template est compilée, elle trouve son chemin vers l'IFC. On peut l'utiliser sans avoir à consommer la syntaxe de C++

(la vidéo a coupé)

Ce fut intéressant, et quand même bien fait. Je me dirige vers quelque chose de probablement un peu mathématique maintenant... J'arrive dans la salle alors de Nicolai Josuttis finit sa discussion sur les λ (c'était sûrement très bien, mais c'est un sujet que je connais de fond en comble)

Vidéo : à venir

Andrew Lumsdaine et Phil Ratzloff – Generic Graph Libraries in C++ 20

Andrew Lumsdaine se présente et situe ses travaux en perspective avec la présentation d'aujourd'hui. Il commence par rappeler ce qu'est un graphe, tout en expliquant à quel point C++, avec son soutien à la programmation générique, convient à cette structure de données. Il met en relief que les graphes sont partout, essentiellement, puis fait l'hypothèse que l'essentiel de ce qui est requis pour développer une bibliothèque de graphes est déjà présent à même la STL, en ce sens que les conteneurs suffisent, ce qui manque est des algorithmes.

Andrew Lumsdaine : un graphe n'existe pas pour entreposer des données mais bien pour traverser des relations. Il dit avoir fait l'erreur par le passé de définir une structure de données, puis réfléchir aux algorithmes, mais ce n'est pas la bonne séquence pour un graphe. Il vise aussi quelque chose de très utilisable (ayant par le passé développé des outils trop Expert-Friendly). Avec Phil Ratzloff (qui ne participe pas à la présentation) et SG19, on vise C++ 26.

Andrew Lumsdaine fait un rappel des principes sous-jacents de la programmation générique, avec le Gradual Lifting menant doucement vers une sorte de vérité algorithmique (on sait qu'on a fini quand il ne reste plus rien à retirer)

Q : comment choisissez-vous les algorithmes à implémenter?

Andrew Lumsdaine : il y a des aspects compliqués (p. ex. : l'algorithme de Dijkstra, parallèle ou pas?). On vise un coeur solide et paramétrable, à l'image de la STL. Des trucs comme depth_first_search() et breadth_first_search() par exemple. En expliquant la technique du Gradual Lifting avec l'exemple de std::accumulate() il met en valeur que la contraposée, la spécialisation des algorithmes, permet de générer du code efficace, des abstractions à coût nul.

Andrew Lumsdaine explique comment transformer un circuit électrique en graphe; c'est très clair, très bien fait. Il montre en quoi la représentation tabulaire est inefficace pour plusieurs raisons, et parle des listes d'adjacences (en montrant des trucs qui permettent de l'implémenter efficacement à partir d'indices plutôt que de noeuds). C'est d'ailleurs un principe : un graphe représente une structure. La conversion des données originales à la représentation {V,E} à la liste d'adjacences peut être mécanisée efficacement par une bibliothèque.

Andrew Lumsdaine examine les textes classiques du Breadth-First Search pour extraire les considérations fondamentales de l'algorithme : un graphe G est un Random Access Range de Random Access Ranges, par exemple, dans lesquels il est possible d'accéder par indice (il y a une liste de requis comme ça). Il montre l'essence de l'algorithme de Dijkstra, qui repose sur une fonction de poids et crée une liste d'adjacences. Il expose une liste d'adjacences comme un concept (bravo!)

Andrew Lumsdaine discute de diverses variantes d'implémentation de ces algorithmes, puis montre les quelques concepts fondamentaux que leurs travaux ont révélé. Certains « algorithmes » semblent être des adaptateurs en pratique, en particulier (BFS pour Breadth-First Search est un adaptateur dont d'autres algorithmes ont besoin). Il montre des résultats vraiment très élégants

Andrew Lumsdaine présente quelques fonctions de fabrication de graphes, pour aider le code client, et montre comment construire des graphes bipartites (très aisément, avec la bibliothèque proposée)

Andrew Lumsdaine compare alors les « concepts » de la Boost Graph Library avec les concepts mis de l'avant par ces nouveaux efforts. La proposition est https://wg21.link/p1907

Andrew Lumsdaine ne prend pas les questions mais se rend disponible pour jaser informellement. C'était vraiment très intéressant comme présentation, je suis bien content d'être venu.

Je me dirige vers la dernière présentation de la journée pour moi, question de voir ce que l'ami Sy Brand a à raconter :) Les deux sont fort sympathiques, d'ailleurs

Vidéo : à venir

Sy Brand et Marian Luparu – What's New in Visual Studio: 64-bit IDE, C++ 20, WSL 2 and More

C'est plus une présentation de Visual Studio 2022, alors je ne note pas tout, mais quelques faits saillants :

J'ai manqué de pile à la fin de la présentation mais c'était chouette.

Jour 4 28 octobre

J'arrive avec vingt minutes de retard (la vie, la santé, les enfants, la litière des chats...). Les Lightning Talks sont en cours...

Vidéo : à venir

Lightning Talks

À mon arrivée, celui qui se déroule (dont je n'ai pas pu voir le nom) parle de revues de code. Ensuite :

 Iwan Smith parle des FPGA

Evelyn Mahasin relate une histoire personnelle de biais cognitif inconscient (ça arrive à tout le monde!)

Javier Estrada parle d'un projet personnel pour documenter C++ au profit d'ingénieurs hispanophones (une version de cppreference en Espagnol!)

Tulio Paschoalin Leao parle de gestion de projets, pour repenser les pratiques d'architecture de système

Chloé Lourseyre explique en quoi elle se considère une experte de C++ (une tranche de vie, vraiment; c'est fait avec humour)

Jens Weller parle de son expérience après avoir tenu Meeting C++ (son Users Group) en ligne. Il a utilisé Hubilo, un outil qui lui semble approprié

Breno Guimarães parle des FlexClass, et de sa tentative d'écrire son propre shared_ptr<T> avec une disposition en mémoire différente de celle proposée par défaut

Thamara Andrade parle d'outils en ligne pour accompagner le développement en C++ (j'en connais plusieurs, mais j'apprends l'existence de onlinegdb, et perfbench)

Matthias Bilger parle du problème de lire des valeurs de configuration en situation où on ne peut pas échouer

(Phil Nash nous montre une photo de Bjarne Stroustrup, avec sa poupée Bobble Head de ... Professor Bjarne Stroustrup :) )

Dominic Pöschko parle d'imprimer les noms des membres d'un type (du code quand même joli : https://github.com/dominicpoeschko/aglio)

Andreas Weis parle de techniques pour estimer rapidement des puissances de deux

Je reste dans la même salle (virtuelle) pour assister à la présentation de Klaus Iglberger, un individu très gentil et très compétent (j'ai le plaisir d'être l'un de ses lecteurs pour un livre sur l'ingénierie logicielle sur lequel il travaille; le ton du livre est très sympathique).

Vidéo : à venir

Klaus Iglberger – Breaking Dependencies: Type Erasure - A Design Analysis

Je manque les premières deux minutes, mais quand j'arrive, Klaus Iglberger discute du fait que le logiciel est changeant par nature, et que ce qui complique le changement est les dépendances. Il cite Kent Beck qui dit que le principal problème du développement logiciel est justement la question des dépendances.

Klaus Iglberger prend le classique exemple des formes à dessiner avec une méthode virtuelle (il blague sur le fait que c'est un exemple surfait, mais bon, tout le monde comprend les enjeux), et introduit des spécialisation comme OpenGLCircle et DirectXCircle... Il ajoute une méthode de forme pour sérialiser... et on se trouve avec des sérialisations Big Endian, Little Endian... C'est hors de contrôle, il y a beaucoup de duplication. On est en contravention directe de DRY.

Klaus Iglberger : l'héritage et le polymorphisme, seuls, ne suffisent pas. Il introduit les schémas de conception, et le fait que leur nom communique une intention.

Klaus Iglberger applique d'abord Stratégie (DrawStrategy, dont dérivent OpenGLDrawStrategy et TestDrawStrategy par exemple), et montre que cela permet d'isoler le changement. Il met en valeur que passer une fonction d'accumulation à std::accumulate() est en fait une application de Stratégie; les accolateurs de nos conteneurs en sont aussi. Stratégie n'est pas limitée à la POO classique.

Klaus Iglberger montre ensuite que le code se répète dans diverses classes malgré tout, mais signale un progrès dans la réduction des dépendances. L'accroîssement du nombre d'indirections (d'appels polymorphiques, d'allocations dynamiques de mémoire) impacte négativement la vitesse du programme et la gestion de la mémoire. De plus, on se retrouve avec une multiplicité de racines polymorphiques (une par type de stratégie)...

Klaus Iglberger dit qu'avec l'effacement de types, on peut arriver à la même qualité de résultat mais sans les coûts. Il cite Sean Parent (Inheritance is the Root Class of All Evil) et Kevlin Henney (dans son texte où il a essentiellement inventé std::any). Il insiste sur le fait que l'effacement de type n'est pas un void*, ou un std::variant, ou un pointeur sur une classe de base... Il définit l'effacement de types comme un constructeur générique plus une interface non-virtuelle plus une combinaison de External Polymorphism (dérivant ShapeModel de ShapeConcept<ShapeModel>) + Bridge + Prototype

Klaus Iglberger montre que cette solution, même si elle fonctionne, implique encore beaucoup d'allocations dynamiques de mémoire (make_unique...) dans le code client. Il déplace ces détails à l'intérieur de Shape et fait un pImpl. Le constructeur générique permet de réaliser Bridge et d'utiliser le compilateur comme générateur de types

Klaus Iglberger en vient au problème de la duplication (il doit ajouter du clonage), ce qui lui donne Prototype

Klaus Iglberger montre ensuite comment ceci lui donne des types valeurs (vector<Shape>, draw(const Shape&)), et en quoi ceci résout le problème des dépendances et nous donne un couplage presque nul.

Klaus Iglberger prend un peu de temps pour référer aux autres présentations sur des sujets connexes, et sur quelques bibliothèques qui aident à réduire la quantité de code à écrire.

Q : pourquoi une fonction friend plutôt qu'une fonction membre?

Klaus Iglberger : pour offrir une meilleure interface

Q : en quoi l'effacement de types se compare-t-elle aux concepts?

Klaus Iglberger : les concepts sont pour le polymorphisme statique, l'effacement de types vise le polymorphisme dynamique

Q : peut-on retrouver les caractéristiques spécifiques d'un type une fois ce type effacé?

Klaus Iglberger : c'est délicat (c'est l'équivalent d'un downcast)

Q : n'est-ce pas triste que les objets à la fin doivent encore savoir à la fois se dessiner et se sérialiser?

Klaus Iglberger : je pense qu'un peut résoudre cet irritant en découplant un peu plus.

Une très belle et très solide prestation de Klaus Iglberger. Les commentaires dans la zone de clavardage sont élogieux, et à juste titre.

Toujours dans la même salle (virtuelle), on a Andrei Alexandrescu qui est toujours brillant, provocateur et amusant. Il arrive au son de You Got Another Thing Coming de Judas Priest :)

Vidéo : à venir

Andrei Alexandrescu – Embracing (and also Destroying) Variant Types Safely

Andrei Alexandrescu envoie un clin d'oeil à John Lakos et dit que ce dernier lui paiera une bière pour chaque utilisation du mot Safely, qui est à la mode cette année (et fait partie du titre de son prochain livre).

Andrei Alexandrescu commencera par expliquer les templates variadiques, puis parler des types variants, puis plus spécifiquement de std::variant, puis du Converse Factory Pattern (tu sais ce que tu crées, pas ce que tu détruis; c'est un terme de son invention)

Andrei Alexandrescu explique les multiples avantages des templates variadiques (algèbre, tuple, verrouillage efficace, concaténation, fabriques incluant emplace, etc.). Plusieurs algorithmes (p. ex. : all_of(), equal(), set_intersection(), etc.) devraient être variadiques (c'est ce que je fais souvent dans mon code), mais il y a des enjeux (écrire une sorte de static_for et itérer verticalement, par exemple comparer les éléments i d'un nombre variadique de tableaux)

Andrei Alexandrescu met de l'avant que les Packs sont un Kind distinct. Tout type appartient à un Kind (ou 14, en pratique, si on compte pointeurs, tableaux, etc.). Les noms de templates sont d'un autre Kind. Il est rare qu'on ajoute un Kind en C++ (il y en avait douze en 1998)... Les Packs sont quelque chose de nouveau; c'est une des rares choses dont on ne peut pas prendre le type : les pointeur sur une fonction operator()-> est l'autre (c'est Sean Baxter qui l'a vu)

Andrei Alexandrescu avec ... avant un nom ça signifie « introduire » et à droite ça signifie « étendre ». S'il y a plusieurs Packs, ils s'étendent en Lock-Step. Il trouve cela décevant. Il suggère d'ajouter template <class ...> struct type_sequence{}; dans le standard car tout le monde le fait de toute manière. On a déjà integer_sequence, qui est au fond un value_sequence (tu peux y mettre des NTTP!). Puis head, puis tail, puis cons comme dans Lisp (évidemment, ça nous ramène à son célèbre livre :) ).

Andrei Alexandrescu : tout ça n'a rien à voir avec ma présentation :) Quand un Pack commence un Matching, ça n'arrête pas (autrement dit : faut que ça arrive à la fin d'une liste de paramètres). C'est du Greedy Matching. Aussi, on a du Fair Matching : un Pack qui n'est pas à la fin d'une liste ne peut pas voir son type déduit. On a

template <class ... Ts, class ... Us> int f(Ts ...ts, Us ...ts);

... où Us est toujours déduit (Greedy Matching) et Ts est toujours explicite (Fair Matching). Si on fait f<int,double>('c'); alors Ts est int,double alors que Us est char. Il ajoute le Corner Case from Hell :

template <class ... Ts, class ... Us, class T> int f(Ts ..., Us ..., T);

... où Us sera toujours vide! Techniquement, c'est illégal (dans le standard) mais aucun compilateur ne le rejette. Ça n'a pas non plus de lien avec sa présentation :)

Andrei Alexandrescu : les types variants sont des Sum Types (union, std::variant). Ils sont partout (colonnes de bases de données; tableurs; JSON; interpréteurs; protocoles; formats de fichiers; etc.). Il note que le Keynote de Herb Sutter en contenait sept! Une bonne implémentation est importante; une bonne implémentation du destructeur est cruciale pour la « performance ». Un type variant aura toujours un destructeur (il sera trivial avec un union). Il montre des chiffres sur l'impact de l'inlining (ou pas) d'un destructeur; on parle de 0,5% pour la page d'accueil de Facebook, ce qui a pour conséquence des millions de dollars par an d'énergie.

Andrei Alexandrescu montre une implémentation de variant<T,Ts...>. Je note qu'il utilise alignas(T) alignas(Ts...) char buf[size]; (deux alignas, chouette!). Il fait remarquer que choisir un type pour la taille peut être une économie importante si on considère l'alignement (citation pour mon cours : Spending Time on your Layout is worth your time). La plupart des opérations sont O(1), hormis visit() et ~variant() qui sont O(n) sur le nombre de types. La fonction visit() est du Multiple Dispatch pur et dur!

Andrei Alexandrescu propose une implémentation qui fait une recherche linéaire sur le nombre de types (on commence à 0, on compare avec le type contenu, si c'est ça on fait le cast sinon on va au suivant). C'est une recherche linéaire récursive à l'exécution. Il montre ensuite une autre implémentation qui fait l'équivalent d'une recherche dichotomique (semi à la compilation, semi à l'exécution; il crédite Pablo Halpern). C'est joli, une dizaine de lignes, appelée par un one-liner dans ~variant() qui passe les bornes 0,sizeof...(Ts)

Andrei Alexandrescu demande ensuite si on peut être O(1). Ben oui, et c'est super beau! Un destroy_element<T>(void *p) qui fait static_cast<T*>(p)->~T() et un destroy_elements<Ts...> qui fait un static const auto dt[]{ { destroy_elements<Ts>... } };. C'est joli, c'est en temps constant, mais on n'aura pas de inlining alors ça ne suffit pas (et ça ne fait pas ce qu'on veut avec les destructeurs triviaux)

Andrei Alexandrescu propose d'y aller avec une sélection d'algorithmes, sur la base du profil de Ts... (recherche linéaire, recherche dichotomique, temps constant). Ça nous donne un mécanisme à mode mixte (un peu de dynamique, un peut de Compile-Time). Il note que le DestroyLog de Pablo Halpern a un défaut : il crée un nombre quadratique de types, alors rien n'est parfait.

Q : quand les templates variadiques ont fait leur entrée, on a vu beaucoup de code manipulant des Packs. Aujourd'hui, je vois qu'il nous manque des outils. Quel est le futur?

Andrei Alexandrescu : si on a type_sequence est compagnie, on connaît les coûts. On a moins de risques de surprises

Q : on a implémenté notre propre variant à ma compagnie. La séquence de if mentionnée plus haut devient un Table Lookup avec Clang. Aussi, un tableau de pointeur de fonctions est une barrière d'optimisation pour tous les compilateurs. Ainsi, la recherche linéaire risque de donner les meilleurs résultats ici.

Bien intéressant tout ça.

J'ai hâte au Keynote de Michael Caisse. C'est quelqu'un de brillant et de drôle. De plus, son Keynote est annoncé comme couvrant les systèmes embarqués. Je manque le début pour des raisons familiales, alors j'arrive sur une blague (The 'T' in "IoT" is for Security)...

Vidéo : https://youtu.be/nrtav9_SKwg

Michael Caisse – Small Inspiration

Michael Caisse présente une citation d'Elon Musk qui prétend avoir écrit le Firmware de nouvelles puces de remplacement pour les Tesla en quelques semaines. Brrr...

Michael Caisse raconte ensuite une expérience de travail où il écrivait du logiciel pour vérifier les tranches de microprocesseurs. Le design et l'impression ne concordaient pas tout à fait (!). Un correctif d'images aidait un peu. Il était tout frais sorti de l'école et travaillait très, très fort... C'est avec les algorithmes qu'il s'en est sorti. Les algorithmes battent le code.

Michael Caisse relate ensuite un cas de reconstruction de genou (Total Knee Arthroplasty) où il travaillait sur un appareil qui permettait de balancer les prothèses. On parle de 4 Ko de RAM. Il voulait utiliser C++, mais il y a des langages « plus bas » et il a été pris avec de l'assembleur avec un soupçon de C. L'ingénierie est un outil. On peut l'appliquer à des tas de problèmes.

Michael Caisse mon cas d'utilisation suivant est une combinaison de MCU (microcontrôleurs) et de FPGA. On voudrait utiliser des algorithmes parallèles (un FPGA est intrinsèquement parallèle!). Les ingénieurs sont vraiment, vraiment mauvais à estimer les coûts (il fait une blague sur l'approche Agile et les sprints)

Michael Caisse aborde ensuite la question de Freestanding, et de la perception que les gens en ont. Plusieurs semblent penser qu'on parle d'un C++ sans système d'exploitation... Pourtant, les principes de C++ sont un Perfect Fit pour Freestanding (pas de langage plus bas, ne pas payer pour ce qu'on n'utilise pas, abstraction à coût nul, correspondance avec le matériel, gestion de la complexité, etc.). Les gens qui font des systèmes embarqués se débarrassent des exceptions et de RTTI spontanément.

Michael Caisse : dans les systèmes embarqués, les temps de compilation ne sont habituellement pas un enjeu (les systèmes sont petits, mais l'étendue -- plateformes, contraintes et autres -- est très vaste)

Michael Caisse : il y a du Freestanding qui est Hosted ou Not Hosted. Il parle des bifurcations (on ajoute des mécanismes, on recommande de ne plus utiliser les anciens), de demande de la part des usagers (les gens des systèmes embarqués ne se plaignent pas)... Il pense que les gens veulent un langage qui réduit les risques d'erreurs. C'est une communauté qui veut qu'on la prenne au sérieux. Implémenter un nouvel algorithme qui dépendrait de coroutines exclurait cette communauté, à cause de la possible allocation. Ajouter une version Non-Allocating d'un service accroîtrait la taille de notre groupe d'usagers heureux.

Michael Caisse : qu'est-ce qui nous inspirait alors que nous grandissions? Il relate sa jeunesse, et se permet de dire Science is the True Magic. Il dit cela en parlant de son grand-père qui était magicien, puis en parlant de son grand-père qui lui a expliqué qu'un cours de sciences, c'est un cours de vraie magie. Il relate ensuite son cheminement personnel (c'est amusant; regardez la présentation, ça vaut la peine). Un peu comme votre humble serviteur, Michael Caisse a commencé à programmer en Logo.

Michael Caisse : ce sont les gens qui inspirent les gens. Il raconte ses visites d'école secondaire pour expliquer ce qu'il fait comme boulot. Il fait un appel à tous pour être inspirants. Il parle ensuite de Minecraft, et de MindStorm, et de la capacité de création et d'exploration qui viennent avec. Il cite son garçon à l'effet qu'un des principaux plaisirs de ces outils est d'exploiter (dans tous les sens du terme!) les nouveaux éléments dès qu'ils deviennent disponibles. Il parle du plaisir d'utiliser des impressions 3D. Il relate d'autres histoires (un club de robotique, par exemple)... et rappelle Sarah Chipp et ses JewelBots de CppCon il y a quelques années (j'étais là, c'était génial!)

Michael Caisse parle de la difficulté de faire collaborer les écoles avec les projets qui impliquent de la technologie. Son Klondike semble être micro:bit de la BBC, qui est très chouette pour les jeunes. Il s'en sert dans ses Meetups et invite les enfants!

Michael Caisse finit par discuter de manières de rejoindre les gens et les inspirer. Il finit avec une belle anecdote, mais je ne vous la raconterai pas; vous irez voir :)

Q : j'ai beaucoup aimé! J'ai été surpris de voir les coroutines et FreeStanding ensemble. J'aimerais inviter tout le monde à ma présentation tantôt sur le sujet!

Michael Caisse : parfait!

Très bien, inspirant, bien fait. Bravo!

Il y a un petit espace d'une trentaine de minutes entre les deux conférence alors je me permets de manger un petit quelque chose avant que la rediffusion de la présentation du toujours pertinent Ben Deane ne débute.

Vidéo : à venir

Ben Deane – "Deducing this" Patterns

Ben Deane : cette présentation se base sur une proposition, https://wg21.link/p0847, dont je suis l'auteur. Le but est de montrer ce qu'on pourra désormais faire en C++

Ben Deane relate tout d'abord la racine du problème (un irritant qu'il a constaté en 2013), décrivant la procédure qui a mené à son adoption pour C++ 23. Son problème est de déduire la catégorie de valeur d'une variable dans une λ pour savoir si on peut la relayer ou la déplacer. Le feature est, dans une méthode d'instance :

struct S { template <class Self> auto f(this Self && moi); }; // this est encore un pointeur; faut utiliser moi dans la fonction, pas this (même pas implicitement)

... et rien d'autre! En gros, ça revient aux bases de C with Classes :) Ça génère toutes les surcharges possibles d'un coup (const, volatile, const volatile, &, &&, etc.). Ça évite de les faire par copier / coller

Ben Deane montre un get() général qui traite const, non-const, etc. On peut utiliser decltype(auto) comme type de retour, ou une métafonction nommée like_t<T0,T1> pour avoir un peu plus de contrôle. Une autre option est forward_like<>, qui est proposé récemment et pourrait aussi être dans C++ 23

Ben Deane : en situation d'héritage, this Self&& déduit le type le plus dérivé connu statiquement. C'est très utile, en particulier pour CRTP (il montre le code, qui est drastiquement simplifié, sans même impliquer de templates!)

Ben Deane : quelques nouveaux trucs :

On peut passer this par valeur (c'est à peu près gratuit, en particulier pour les objets Stateless ou les objets légers comme string_view)

On peut enchaîner des mouvements pour créer des séquences complexes d'opérations ou éviter des problèmes de gestion de vie (comme dans un for sur des intervalles), cool!

Les petits objets (surtout s'ils sont vides) sont optimisés pour cette raison

Une λ peut parler d'elle-même plus aisément

Éliminer certains cas de types SFINAE-Unfriendly

Ben Deane : les λ gagnent beaucoup au change, en particulier les λ récursives (qu'on peut maintenant écrire sans tricher, mais il faut qu'elles soient génériques car on ne peut pas nommer le type :) ). La syntaxe choisie tient entre autres beaucoup au fait que le support de λ était essentiel. Il montre un très beau visiteur d'arbre binaire (Ben Deane fait remarquer que la λ n'est pas nécessairement un foncteur classique, car le compilateur peut faire des trucs comme faire disparaître des membres ou jouer avec leur ordre de déclaration). On peut maintenant contraindre la catégorie d'une λ (par exemple, à des lvalue)

Ben Deane : on peut maintenant contraindre nos classes dérivées en appliquant un concept à un deduced this (oh, j'aime beaucoup!)

Q : peut-on faire une manoeuvre du genre sur un destructeur pour contraindre les types des classes dérivées?

Ben Deane : je pense que oui, mais faudrait l'essayer

Ben Deane propose une implémentation très jolie du Template Method (idiome NVI)

Ben Deane : le Name Lookup dans la fonction change un peu : c'est une méthode d'instance mais on doit utiliser l'objet nommé, comme dans une fonction globale. Faut faire attention au Shadowing (vu que le type déduit est le plus dérivé et peut masquer des noms du parent). Si c'est de l'héritage privé, on n'a pas accès au parent explicitement dans la fonction et ça peut être compliqué (ça se gère... avec un transtypage du langage C, qui ignore toutes les règles...)

Ben Deane : on peut utiliser ces méthodes d'instance comme des fonctions globales (pas besoin de .* ou de ->*)

Ben Deane propose une liste des pour et des contre (il y en a toujours). Il y a beaucoup de « pour » (conséquent, implémente plusieurs Patterns plus aisément...). Il faut toutefois se familiariser avec les templates, like_t, forward_like_t, il peut y avoir des temps de compilation un peu plus longs. On se trouve avec plus de surcharges (implicites) qu'avant. Ces méthodes ne peuvent pas être virtuelles (pour le moment)

J'ai manqué les questions / réponses pour raisons familiales, mais ce que c'était bon comme présentation! Bravo! Et ce mécanisme ouvre des tas de nouvelles avenues pour du code simple et élégant :)

J'arrive à la présentation d'Aditya Kumar juste comme cela débute.

Vidéo : à venir

Aditya Kumar – Code Size Compiler Optimizations and Techniques for Embedded Systems

Aditya Kumar parle des coûts de programmes trop lourds en espace, et suggère des outils dont des programmes nommés size, strings et Bloaty (celui-là de Google). Ça semble être une présentation préenregistrée. Il examine ensuite des options de compilation pour réduire la taille des binaires (et certaines qui peuvent l'accroître). Ça dure presque vingt minutes...

Aditya Kumar aborde ensuite ce qu'il est possible de faire dans le code source. Déplacer le code des .h vers les .cpp, réduire la hauteur des hiérarchies de classes, utiliser constexpr, réduire le nombre de classes polymorphiques, profiter de EBCO, etc. Aucun exemple...

Aditya Kumar poursuit avec les annotations non-portables... Ensuite, il suggère d'utiliser std::list au lieu de std::vector pour réduire la taille (il va avoir une surprise s'il mesure l'impact réel sur la taille!). Il suggère aussi d'utiliser des algorithmes moins coûteux en espace, comme un tri à bulles plutôt qu'un tri fusion. En gros, il suggère d'éviter les algos récursifs. Il suggère d'appeler les fonctions standards si possible plutôt que de les réécrire (Ok, ça se tient)

Aditya Kumar propose d'autres étapes : utiliser strip sur les symboles, compresser le code moins utilisé et le placer dans une DLL, etc.

Aditya Kumar suggère des améliorations au langage : un noexcept conditionnel, pour réduire la taille en enlevant le code pour récupérer; renommer les fonctions pour que les noms hashés soient plus courts; réduire le inlining des zones froides; fusionner les fonctions semblables; etc.

Je n'ai pas noté les questions.

Bon, ce n'était pas ma préférée cette semaine, mais en toute honnêteté j'ai été gâté par la qualité générale. Et la dernière de la journée est par John Lakos, qui nous tient toujours sur nos gardes... D'ailleurs, il bavarde avec des gens sur place et leur annonce une présentation un peu « méta ».

Vidéo : à venir

John Lakos – Embracing `noexcept` Operators and Specifiers Safely

John Lakos insiste d'office sur le Safely. Il présente ses deux livres existants... et le prochain. En fait, il dit ne pas avoir écrit ce nouveau livre; que c'est le monde qui a écrit le livre pour lui. Il parlera à la fois de l'opérateur noexcept et de la spécification noexcept.

John Lakos positionne Safely un peu comme le faisait son co-auteur Vittorio Romeo plus tôt cette semaine, en termes de risques de se faire mordre à l'usage.

John Lakos présente noexcept en utilisant la forme de son livre (en passant, je ne suis pas 100% d'accord avec sa définition pour la spécification, mais je comprends sa lecture étant donné sa position architecturale; pour l'opérateur, c'est plus intéressant). Il a un cas étrange avec static_assert(noexcept(sizeof(throw,1)),""); alors que !static_assert(noexcept(0?throw:),""); et !static_assert(noexcept(1?throw:),"");

John Lakos explique (avec véhémence) que le fait qu'une fonction noexcept puisse lever une exception l'enrage.

John Lakos : les fonctions autogénérées sont noexcept. Les fonctions spéciales sont noexcept(false) sauf pour le destructeur qui est noexcept(true). Appliquer noexcept au mouvement est une bonne chose (ça va trop vite pour prendre des notes :) ). Son exemple de push_back() est différent du mien (mais équivalent). C'est pas une mauvaise idée

John Lakos présente une fonction sinus() écrite manuellement, et place un static_assert sur noexcept du code à l'interne, mais passe la remarque (pertinente!) que c'est pas utile là. Le recours à noexcept va sur une signature

John Lakos : noexcept évalue toute l'expression, incluant les effets de bord. Ça peut jouer des tours. Il montre un template<class T> avec un noexcept(noexcept(T(T()))) où on évalue un constructeur par défaut (existe-t-il?) ... et un destructeur! L'opérateur noexcept n'examine pas la définition des fonctions. Il suggère de préférer les traits (il a raison!)

... il passe à peu près 350 diapos en une heure, mieux vaut le regarder :)

John Lakos : point important, noexcept n'accélère probablement pas l'exécution (c'est sur le Cold Path), hormis par voie de réduction de la taille du code

(on est tous « sonnés » par la masse d'informations, alors il n'y a qu'une seule question, de Dave Abrahams, l'idéateur de la garantie forte; il parle de la violation de la règle du Zero Overhead)

Bon, c'était divertissant! Reste à finir de préparer ma propre présentation pour demain :)

Jour 5 29 octobre

Un peu de brouhaha ce matin qui me ralentit dans ma préparation pour ma propre présentation cet après-midi. Hé la la...

Vidéo : à venir

Bryce Adlestein-Lelbach – C++ Standard Parallelism

Bryce Adlestein-Lelbach : ce qui rend C++ portable aujourd'hui est son ensemble d'outils de concurrence (modèle mémoire, modèle d'exécution, primitives de synchronisation, etc.). Toutefois, le seuil d'entrée est trop haut, et pour cette raison plusieurs gens se limitent au code séquentiel

Bryce Adlestein-Lelbach fait une parenthèse pour expliquer que le standard de C++ est descriptif, pas prescriptif. Les outils de parallélisme et de concurrence sont en grande partie dans la bibliothèque plutôt qu'intégrés au langage, ce qui accroît sa portabilité (évidemment, la machine abstraite, le modèle mémoire et quelques outils comme les atomiques sont intégrés au langage, mais on cherche à garder la liste au minimum)

Bryce Adlestein-Lelbach parle des algorithmes et des ranges, dans leurs versions séquentielle et parallèle. Les politiques d'exécution permettent de spécifier le laquo; quoi raquo;, pas le laquo; où raquo;. (j'ai manqué quelques minutes ici pour gérer une urgence). Il est possible d'utiliser span pour modéliser des matrices, et montre que construire des foncteurs composites permet d'accroître le débit (ranges::view::transform aide ici)

Bryce Adlestein-Lelbach fait remarquer que tous les algorithmes parallèles de C++ 17 ont deux défauts : on ne sait pas où ils s'exécutent, et ils sont tous de type fork-join donc un enchaînement d'algorithmes parallèles bloque à la fin de chaque étape. Il présente le modèle sender / receiver de std::execution : schedulers are handles to execution context; senders represent asynchronous work; receivers receive asynchronous signals. Il fait ensuite un catalogue des senders les plus importants, c'est très clair

Bryce Adlestein-Lelbach fait une démo d'un inclusive_scan() parallèle et générique pouvant opérer sur n'importe quel Scheduler. C'est joli, mais pas trivial.

Bryce Adlestein-Lelbach enchaîne avec std::mdspan de C++ 23. C'est Non-Owning (pointeur et métadonnées pour décrire les dimensions), et ça peut être utilisé Compile-Time ou Run-Time. C'est très léger et on peut paramétrer les mécanismes d'accès et de parcours. Ça utiliser un template <size_t...> struct extents; pour les dimensions statiques et des dynamic_extent pour les dimensions connues à l'exécution (on peut mêler les deux). Un mdspan a quatre paramètres (T, Extents, LayoutPolicy -- on a Right pour C++, Left pour Fortran -- et AccessorPolicy) mais les deux premiers seulement sont nécessaires. On utilise la nouvelle syntaxe md[i,j,k] de C++ 23.

Bryce Adlestein-Lelbach explique comment la syntaxe choisie permet d'utiliser mdspan avec un tableau brut, une matrice Eigen, une matrice Blas, etc. Il montre ensuite comment faire des Slices à partir d'un mdspan. Les std::tuple et CTAD sont mis à contribution.

Bryce Adlestein-Lelbach aborde la question des algorithmes d'algèbre linéaire. Pour respecter les standards de facto, nous standardisons Blas mais avec une interface appropriée au langage. Il donne des exemples (ses diapos sont bien : il y a beaucoup de tableaux multidimensionnels, mais les couleurs des indices sont différentes ce qui facilite le repérage)

Il y a un petit nombre de questions. Il est probable que mdspan soit dans C++ 23; pour les senders / receivers, c'est moins sûr mais on essaie. On jase de standardisation d'opérateurs pour les divers mécanismes de relais de calcul (operator|() se lit bien mais il y a des imperfections, en particulier sa priorité). Bryce Adlestein-Lelbach parle brièvement de tag_invoke, mais sans plus.

Vidéo : à venir

Marshall Clow – Down The Rabbit Hole: An Exploration of Stack Overflow Questions

Marshall Clow explique qu'il traitera non pas de Stack Overflow, mais bien de questions posées par des gens et de manières de les aider. En particulier, il fait remarquer que les gens tendent à laquo; gosser raquo; avant de demander et, pour cette raison, contraignent la question, et bloquent sans s'en apercevoir des avenues fécondes de solution.

Le premier problème posé est une questions difficile à lire et à comprendre. La première étape est de reformuler la question pour bien saisir les enjeux (Marshall Clow relate une boutade de Lisa Lippincott : si tu veux de meilleures réponses, le truc est de poser de meilleures questions). Après un peu de travail, et un peu d'analyse, une solution au problème vécu a été trouvée... mais ce n'était pas la réponse à la question. Il pose une autre question, à savoir : pour trouver le deuxième plus petit élément d'une séquence v, lequel des deux ci-dessous serait préférable? (les deux placent le plus petit dans v[0] et le deuxième plus petit dans v[1])

nth_element(begin(v), next(begin(v)), end(v));

partial_sort(begin(v), next(begin(v), 2), end(v));

Marshall Clow dit que les gens tendent à dire nth_element(), car la complexité algorithmique est linéraire, O(n), en moyenne, alors que partial_sort() est de complexité approximative O(n * log_n). Cela dit, dans ce cas, le nombre d'opérations (en particulier, les comparaisons et les swap) est un tenir compte. Il a écrit une petite classe qui fait des comparaisons et journalise le nombre d'appels... partial_sort() faisait, pour ce problème bien spécifique, beaucoup moins de comparaisons et beaucoup (considérablement!) moins de swap() que nth_element(). (quelqu'un souligne que vu que le problème s'applique à des string, les swap() vont coûter beaucoup moins cher que les comparaisons)

Marshall Clow poursuit en écrivant un algorithme spécialisé plutôt qu'un algorithme général, étant donné le caractère un peu spécifique du problème. En pratique, on peut s'en tirer avec un maximum de deux swap(). L'algorithme est présenté, c'est simple. À la question laquo; combien de comparaisons cet algorithme fera-t-il? raquo; la réponse est laquo; ça dépend des données raquo; et il peut y avoir une grosse différence entre le meilleur et le pire cas. Il se trouve que le nombre de comparaisons était exactement le même qu'avec partial_sort() (partial_sort() fait plus de swap() par contre). Cela dit, en général, ça ne vaut probablement pas la peine :) Marshall Clow fait remarquer qu'en général (quand on ne cherche pas spécifiquement le 2e élément), nth_element() sera probablement meilleur que partial_sort()

Marshall Clow : la deuxième question est un individu qui veut appliquer l'idiome erase-remove sur une string et utilise std::ispunct, or il y a une anbiguïté (il y a plusieurs ispunct dans la bibliothèque). On peut vouloir retirer <locale> du lot, mais d'autres en-têtes peuvent l'inclure... Le problème de fond est qu'en général, tu ne peux pas prendre l'adresse d'une fonction de la bibliothèque standard. Si on a C++ 11, la solution est simple : une λ tout simplement. Ça résout le problème, mais pas comme il était énoncé dans la question.

Marshall Clow fait une petite parenthèse sur les restrictions applicables aux paramètres de std::ispunct() (entre autres, si char est signed, un négatif nous mène au comportement indéfini), et fait l'éloge de cppreference au passage. Il a bien raison (quelqu'un dans la salle demande pourquoi on se préoccupe du comportement indéfini; Marshall Clow a un petit rire nerveux et un frisson passe dans la salle)

Marshall Clow : la dernière question est d'un individu qui a une map<K,V> où K est un tuple<A,B,C> mais ne souhaite pas considérer le C. Il souhaite une recherche logarithmique, pas linéaire. Évidemment, le réflexe ici est d'utiliser un comparateur transparent (un foncteur, plusieurs surcharges du prédicat binaire), et d'implémenter les comparaisons tuple<A,B,C> avec tuple<A,B> (et l'inverse, et les deux homogènes), puis appeler equal_range(tuple<A,B>(a,b));, mais il faut vraiment s'assurer que l'ordre de tri résultant corresponde à celui utilisé pour garder la map en ordre

Marshall Clow finit avec quelques rappels sur la méthodologie, mais le temps file...

J'ai manqué les questions et les réponses car une succession d'incidents se produisent à la maison. C'est une journée compliquée... Marshall Clow a fait un excellent travail, comme c'est son habitude. Il explique bien les choses complexes.

Je ne pourrai pas bien suivre mon ami Walter E. Brown comme je le voudrais car je dois faire des retouches de dernière minute sur mes diapos (je suis juste après lui) et que je devrai roder la mécanique de ma présentation avec l'équipe là-bas avant la fin de sa présentation. C'est triste, car Walter E. Brown est un excellent présentateur.

Vidéo : à venir

Walter E. Brown – Correctly Computing min, max, and More: What Can Go Wrong?

Walter E. Brown rappelle que pour min, max et d'autres algorithmes du genre, la valeur retournée est incorrecte (de l'avis de Stepanov lui-même) car min(a,b) retourne b si a==b (il s'exprime a<b?a:b essentiellement). Il montre des exemples de types pour lesquels cela pose problème (p. ex. : une classe student avec une propriété id qui n'est pas manifeste et n'intervient pas dans le calcul de operator<(const student&,const student&)

Walter E. Brown : sauf si nous avons une raison de faire autrement, dans le cas où a==b, min(a,b) devrait retourner a et max(a,b) devrait retourner b. Cela permet une forme de stabilité algorithmique (un algorithme stable ne modifie pas l'ordre des éléments de la séquence qui étaient dans le bon ordre au préalable), Il donne l'exemple de merge() entre deux séquences : il faut garder l'élément de la première séquence à moins d'avoir une raison de ne pas le faire. Il utilise un prédicat out_of_order(a,b) au lieu de a<b pour clarifier son intention. Il présente sort2(a,b) qui place a et b dans l'ordre, puis sort3(a,b,c) qui place a,b,c dans l'ordre, et son implémentation est... un tri à bulles :)

Walter E. Brown nous convie à une réflexion profonde sur le sens de operator<(a,b) (ou std::less<>{}), puis nous amène à réfléchir à la définition de is_sorted_until() sur la base de sa spécification. Il propose ensuite de prendre l'habitude d'exprimer le désordre par un prédicat out_of_order (une λ locale à la fonction, souvent) et il recommande de lui passer des itérateurs (qui se copient bien) plutôt que des valeurs (sont-elles copiables?). Il utilise parfois precedes(a,b) ce qui ressemble à ma propre notation quand j'explique ces manoeuvres en classe

Walter E. Brown montre ensuite une signature qui utilise un comparateur avec un type par défaut, std::ranges::less, qui est un comparateur hétérogène, mais montre que cette technique ne peut être appliquée dans la bibliothèque standard, et pose parfois problème

Walter E. Brown poursuit avec six façons d'écrire operator< dans la bibliothèque standard : less<T> (comparaison homogène, C++ 98), less<void> (comparaison hétérogène, C++ 14), ranges::less (C++ 20), cmp_less (C++ 20), isless (C++11), totalOrder (C++ 20). C'est intéressant, je ne les connaissais pas tous. Walter E. Brown nous montre des implémentations possibles pour chacun.

(j'ai manqué la fin, mais c'était très bon)

Walter E. Brown :

Walter E. Brown :

Walter E. Brown :

 

C'est une journée avec une continuité algorithmique pour moi, avec Marshall Clow et les problèmes solubles par voie algorithmique, puis Walter E. Brown. Dans une certaine mesure, ma présentation va dans la même veine.

Vidéo : à venir

Patrice Roy – Evolving a Nice Trick

C'est ma présentation alors je ne prends pas de notes (pas le temps!)

Je dirais qu'en gros, ça s'est bien passé, mais nous avons eu un crash de GatherTown à la fin de la présentation alors tout le monde au colloque s'est trouvé dans le noir, donc je n'ai pas vraiment au de questions / réponses. Le système est retombé sur ses pattes une quinzaine de minutes plus tard.

Note a posteriori : j'ai eu accès aux commentaires dans la zone de clavardage quelques jours plus tard et c'était très, très positif... Ouf!

Ouf, une bonne chose de faite. Direction : la présentation de Daisy Hollman.

Vidéo : à venir

Daisy Hollman – What You Can Learn From Being Too Cute: Why You Should Write Code That You Should Never Write

J'ai manqué quelques minutes car mon ordinateur a gelé (plus de clavier, plus de souris). Semaine difficile pour ma machine!

Daisy Hollman dit que cette présentation est faite de plusieurs mini présentations, et pleine de trucs...

Daisy Hollman : premier truc : on peut itérer à rebours à travers un Pack... (elle fait un Fold sur operator= et l'applique à une λ qui retourne un truc affectable... comme type_identity<void> :) !). Ce n'est pas un enjeu d'associativité cela dit (intéressant), mais bien

Daisy Hollman : deuxième truc : utiliser une IIFE statique locale à une fonction en tant que execute_once... mais si la λ lève une exception, l'objet n'est pas construit, alors si on place un try ... catch dans la fonction, on peut avoir un execute_n! Et si on lève une exception à chaque fois, ce code ne sera pas concurrent!

Daisy Hollman : cinquième truc (à cause du temps) : utiliser CTAD et les guides de déduction pour s'en sortir sans constructeur en tant que tel. Elle montre que le guide de déduction n'est plus nécessaire depuis C++ 20, mais montre quelques manoeuvres supplémentaires possible... incluant utiliser des Designated Initializers

Daisy Hollman : itérer à travers les membres d'un Pack. Elle utilise la nouvelle syntaxe des λ génériques avec paramètres de types explicites pour simplifier l'écriture

Daisy Hollman : accéder le dernier membre d'un Pack (un truc de Richard Smith). C'est pervers mais brillant!

Il y a quelques questions sympathiques, mais le coeur du propos est passé à ce stade.

C'était amusant. Le dernier truc était le plus intéressant du lot à mes yeux.

Vidéo : à venir

Bob Steagall – UB, ODR, ADL, ICE, SFINAE, IFNDR... WTF?

Bob Steagall présente ces termes techniques et leur sens. C'est une présentation de vocabulaire, mais Bob Steagall fait du bon travail. J'ai eu un empêchement qui m'a forcé à quitter mon poste, et je ne suis revenu que pour la période de questions, mais je vais essayer de regarder le tout ultérieurement (c'est un bon vulgarisateur).

À mon retour, je me dépêche d'aller vers le dernier Keynote de la semaine. Sean Parent est souvent très marquant dans ces situations.

Il y a bien sûr une publicité...

Vidéo : à venir

Sean Parent – Warning, std::find() is Broken!

Sean Parent relate une histoire de son début de carrière. Il avait réglé un bogue, mais son patron lui a dit Great fix, but know I want you to think about how we can help other not to make the same mistakes in the future. Il cite aussi Alexander Stepanov qui disait qu'il est plus difficile de comprendre pourquoi le logiciel fonctionne que de comprendre pourquoi il ne fonctionne pas.

Sean Parent aborde ensuite Assigning Meaning to Programs par Robert W. Floyd, sur les méthodes formelles, puis An Axiomatic Basis for Computer Programming de Tony Hoare (que j'ai lu). La logique résultante est Floyd-Hoare Logic : P{Q}R (P est une précondition, un prédicat; R est une postcondition, aussi un prédicat; Q est une opération). L'enjeu est la composition des opérations

Sean Parent fait ensuite un peu de rappel pour la logique propositionnelle, et introduit la logique Floyd-Hoare qui tient compte des débordements et des ensembles finis. Il montre comment cette notation permet entre autres de modéliser le travail des analyseurs statiques.

Sean Parent poursuit par Design by Contract de Bertrand Meyer (que j'ai lu il y a longtemps). Le contrat en Eiffel fait partie de l'interface de la fonction. Ce texte a aussi introduit le concept d'invariant. Il y a des limites : le contrat doit s'exprimer en code, et il y a un risque de transformer une fonction linéaire en fonction quadratique. On peut bien sûr enrichir cela par de la documentation, et le principe rend les méthodes formelles accessibles à toutes et à tous.

Sean Parent : Hyrum Wright nous a appris que peu importe ce qu'on pense du contrat, s'il y a un bogue, quelqu'un va finir par en dépendre. Il écrit un appel à remove_if() qui vise à enlever le premier impair d'une séquence. Son premier jet comporte un bogue, car il utilise un prédicat Stateful (bobo). Il retire l'état de la λ qui lui sert de prédicat, et prend cet état par référence... Ça fonctionne, mais le standard exige qu'un prédicat soit régulier...

Sean Parent introduit une définition de Safety & Correctness. Une opération est Safe si elle ne peut pas mener à du comportement indéfini (directement ou indirectement), et ce même si les préconditions sont brisées. Le code qui brise une précondition est incorrect. Conséquemment, Safety vise le comportement du logiciel même dans ce cas.

Sean Parent : Safety : une opération bien implémentée est telle que si les préconditions sont respectées, alors soit elle réussit et remplit ses postconditions, soit elle échoue et rapporte l'erreur.  Les objets mutés en cours de route doivent être laissés dans un état connu et déterminable (je n'arrive pas à noter les détails de Correctness, mais ne pas l'atteindre a un impact viral). Il donne un exemple très mal avisé.   

Sean Parent : une implémentation peut, si elle le souhaite, poser des gestes sensés quand le code client viole une précondition, mais est-ce sage? Il discute des entiers signés ou non-signés, où il y a plus de préconditions sur les signés que sur les non-signés. Il montre l'algorithme de bresenham_line() qui manipule des entiers signés et comprend plusieurs assertions. Il la manipule pour l'accélérer, et explique pourquoi cette version (sans instructions de branchement) peut être une quinzaine de fois plus rapide en pratique, mais il y a des risques... Il montre quelques écueils des calculs sur les entiers. Il se questionne sur l'intérêt du comportement indéfini

Sean Parent : une précondition forte accroît notre capacité de raisonnement sur le code, et peut être porteur de sens, mais en retour elle permet des variations de comportement entre les implémentations

Sean Parent passe ensuite à la programmation générique de Dave Musser et Alexander Stepanov, puis aux concepts par James C. Denhert et Alexander Stepanov. Les concepts sont un ensemble nommé de requis : des axiomes, des contrats, des garanties de complexité. Les concepts de C++ 20 se limitent au volet syntaxique du contrat. Il décrit le contract de equality_comparable. Ensuite, il explique la puissance des concepts, qui donnent un sens à un ensemble sans limite d'opérations. Un paramètre doit satisfaire un concept. Une discussion de requis vs garanties suit; il examine les requis de distance(d,f). Il discute de ce dont émanent les concepts, et rappelle que les concepts ne spécifient pas une implémentation : ils spécifient un sens

Sean Parent examine enfin std::find(). Il revient à la documentation d'origine (celle de SGI). Il fait remarquer qu'on avait EqualityComparable à l'origine, et qu'on utilise operator== désormais. À titre de rappel, NaN n'est pas equality comparable. Il critique le manque de rigueur de la définition de Cpp17Comparable pour qui double ne satisfait pas le concept (il y a eu beaucoup de discussions là-dessus)

Sean Parent nous rappelle que le domaine d'une opération n'est pas la même chose que le type de ses paramètres. Dans sa définition de EqualityComparable (le vieux, du moins), il faut qu'au moins une valeur satisfasse le concept. C'est NaN qui fait mal, alors il y a un requis pour std::find() que la valeur !=NaN et qu'aucun élément de la séquence ne soit NaN. Dans std::find(), c'est == qui nous fait mal...

Sean Parent : on veut une délinéation claire entre le correct et l'incorrect. Quand nos spécifications sont insuffisantes, la loi d'Hyrum se faufile... Il recommande une lecture (en profondeur!) des spécifications, et beaucoup de prudence quand on s'approche des limites de la spécification (être astucieux demande de valider plus).

Sean Parent : réfléchissez au sens de votre code. Assurez-vous que les noms utilisés portent la sémantique attendue. Spécifiez les préconditions par un concept, et associez-leur une sémantique (il fait remarquer que nous n'avons pas de mots dans le standard pour parler du domaine du destructeur et de l'affectation)

J'ai manqué les questions / réponses (nous avions une famille adoptante à la maison pour des chatons). Un autre Keynote qui fait réfléchir... Une bonne semaine, dans l'ensemble.

 

Et après...

 


Valid XHTML 1.0 Transitional

CSS Valide !