Je participe à cette rencontre à distance encore une fois (ces dernières années je n'ai pas réussi à mettre de côté les fonds requis pour aller à ces rencontres en personne, malheureusement). La rencontre de cette semaine se tenant à Brno (Tchéquie), le décalage horaire sera violent (les rencontres débutent à 2 h 30 du matin heure de Montréal). Mes dossiers n'ont pas progressé ces derniers mois alors je participerai mais je ne présenterai pas de propositions moi-même. Cette semaine, le centre d'attention est C++ 29.
Je devrais passer ma semaine chez CWG comme à mon habitude. Une proposition assez conséquente, p3891, propose rien de moins qu'une refonte importante de la grammaire du langage, en particulier tout ce qui a trait aux séquences, aux aspects optionnels et aux groupes de type « un parmi ceux-ci ». Ça risque de prendre beaucoup de temps et d'énergie chez CWG.
Tout ce qui suit est anonymisé et simplifié, dans le respect du code de conduite d'ISO en vigueur lors de nos rencontres.
Ce document est incomplet (j'ai manqué de temps). Je compléterai dès que possible.
Pour les « journaux de voyage » d'autres participants, voir :
(j'ai manqué la plénière; mon réveil a sonné mais n'a pas réussi à me réveiller; j'arrive alors qu'on discute de P2952 – auto& operator=(X&&) = default)
La proposition est de permettre d'utiliser la déduction de types et =default dans plus de cas que operator<=>(), par exemple operator==() et operator=(). À mon arrivée (vers le milieu de la matinée), on discute des règles pour déduire le type de retour. L'enjeu est que auto doit donner ce qu'on s'attend pour le type (dans l'affectation, une référence sur le type l'opérande de gauche; sur operator==(), on veut bool) et il faut que les règles nous mènent à la bonne décision. On veut aussi que =default soit équivalent à =delete si, par exemple, on implémente l'affectation de copie mais l'une des données membres est incopiable.
Le but ici est de permettre de remplacer ceci :
[&]{
B b;
b.a = 5;
b.b = 6;
return b;
}();... par cela :
B{.a=5, .b=6 }... dans le cas où B n'a pas de constructeur (on vise manifestement un meilleur support des agrégats). Par exemple :
struct A { int a; };
struct B : A { int b; };
auto b = B{.a=1, .b=2 };On examine la grammaire qui semble, aux yeux de certains, demander de consommer la liste d'initialisation au complet avant de prendre une décisions sur la production à choisir (il y a des lectures divergentes de la grammaire ici). Ça semble être un changement mineur, mais sur le plan grammatical il y a beaucoup de conséquences à ce changement (quand on lit une accolade ouvrante, les possibilités de productions deviennent soudainement beaucoup plus nombreuses).
(un des membres présents sur place signale que la pièce a une fenêtre, chose rare chez CWG, et que c'est une distraction...)
Une partie des efforts investis vise à éviter des ambiguïtés. Par exemple :
struct A { int a1, a2; };
struct B : A { int b; };
struct C : A { int a1; };
B v1 = B{ .a1=1, .b=2 }; // the explicitly initialized elements are [A, B::b]
B v2 = B{ .a1=1, .a2=2, .b=3 }; // the explicitly initialized elements are [A, B::b]
B v3 = B{ A{ 1, 2 }, .b=3 }; // the explicitly initialized elements are [A, B::b]
B v4 = B{ A{}, .a2=1, .b=3 }; // error: A initialized two different ways
C v5 = C{ .a1=4 }; // the explicitly initialized elements are [C::a1]
La gestion des agrégats dont l'initialisation est « aplatie » est discutée :
struct A { int a; };
struct B : A { int b; };
struct C : B { int c; };
// the A element is initialized from {.a=1}
B x = B{ .a=1 };
// the B element is initialized from {.a=2, .b=3}
// which leads to its A element being initialized from {.a=2}
C y = C{ .a=2, .b=3, .c=4 };Dû au modèle objet de C++, il est important que les initialisations soient appliquées en ordre de déclaration, et les exemples préexistants doivent être ajustés pour tenir compte de cette nouveauté qui permet d'utiliser un designater initializer pour une classe de base :
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error: designator order does not match declaration order
A b{.x = 1, .z = 2}; // OK, b.y initialized to 0
+ struct B : A { int q; };
+ B e{.x = 1, .q = 3}; // OK, e.y and e.z initialized to 0
+ B f{.q = 3, .x = 1}; // error: designator order does not match declaration order
+ struct C { int p; int x; };
+ struct D : A, C { };
+ D g{.y=1, .p=2}; // OK
+ D h{.x=2}; // error: ambiguous lookup for x
+ struct NonAggr { int na; NonAggr(int); };
+ struct E : NonAggr { int e; };
+ E i{.na=1, .e=2}; // error: the lookup set for na finds NonAggr, which is not an aggregate base of E(pause du dîner; il est 6 h du matin à Montréal; j'en profite pour réveiller Ludo qui doit aller à l'école et faire un petit-déjeuner à mon amoureuse Za)
On poursuit les travaux. Hubert Tong avait des préoccupations sur certaines phrases mais n'était pas présent ce matin alors on profite de son arrivée pour éclaircir celles-ci. En gros, en transformant le texte, on semble avoir perdu un peu du sens du texte d'origine et c'est embêtant. On travaille sur la formulation un peu plus de 30 minutes...
Certains ajustements de dernière minute sont faits (la proposition visait à l'origine C++26 alors certains aspects parlent de C++23 qui était alors le standard officiel, mais nous sommes désormais à C++26 alors on fait une mini mise à jour de ces passages).
L'enjeu ici est le souhait de faire en sorte qu'une exception levée sur une fiber demeure sur cette fiber. Les changements proposés sont petits mais on travaille pour faire en sorte que le texte soit le plus clair possible. L'interaction avec setjmp()/longjmp() est aussi explorée (changer de fiber en chemin est UB). On mentionne (comme je l'ai appris à la dure à travers certaines de mes propres propositions par le passé) qu'il est difficile de bien parler de certains aspects de l'exécution d'un programme sans parler de la pile d'exécution, or la machine abstraite de C++ ne suppose pas de pile...
On examine ensuite l'interaction entre les fibers et les signal handlers. C'est un autre truc qui est plus ou moins clairement défini dans le langage, alors c'est difficile à bien circonscrire ici.
Davis Herring fait remarquer qu'il nous faut des mots pour éviter qu'une implémentation n'évalue de manière préventive du code avant que ce ne soit correct de le faire, ou pour empêcher que la résomption d'une fiber ne se fasse avant son point de suspension, ce qui ne semble pas être le cas en ce moment. Jens Maurer précise que les mots actuels ne semblent pas empêcher qu'une implémentation ne change de fiber profondément dans une séquence d'appels de fonctions. Il est possible que ces possibles problèmes tiennent à l'interaction entre coroutine et fiber, plus spécifiquement au fait qu'une coroutine peut changer de thread alors qu'une fiber ne le peut pas, or un thread exécute un fiber... L'auteur va travailler sur cet aspect plus tard.
On a un passage qui dit « a thread is always running exactly one fiber ». Hubert Tong questionne le choix de « running » qu'on utilise très peu dans le standard. Davis Herring fait remarquer que « running fiber » n'est pas un terme défini. On discute des interactions entre fibers en les algorithmes par_unseq (brrrr...). DH suggère d'écrire fiber resumption is vectorization-unsafe pour le moment.
Le texte actuel permet d'utiliser des ref-qualifiers sur l'affectation (de mouvement, mais aussi sur l'affectation de copie; on critique le titre de la proposition). Le proposition est de l'interdire. C'est un truc très niché et qui est source de confusion, car cela permet des aberrations comme :
A& operator=(const A&) && = defaultOn passe beaucoup de temps sur la formulation des phrases (il y a beaucoup de sortes de références dans chaque énoncé, ça peut devenir lourd). Éventuellement, on arrive à la pause de l'après-midi alors j'en profite pour faire une tournée de distribution de verdure aux rongeurs et aux lagomorphes de la maison (mon amoureuse me demande d'en donner un peu à Patapon, le conure qui « m'aide » pendant nos travaux en se perchant sur mon épaule et en attaquant mes écouteurs, alors je procède).
Je laisse des remarques sur des petites erreurs remarquées sur des propositions ce matin (j'ai manqué le début alors j'ai vu des trucs a posteriori).
L'annexe C contiendra cet ajout (avec explications) :
struct S{
S& operator=(const S&) && = default; //ill-formed; previously well-formed
};
struct T{
T& operator=(const T&) const = default; //ill-formed; previously well-formed but deleted
};
struct U{
U& operator=(const U&&) = default; //ill-formed; previously well-formed but deleted
};Ça prend forme.
La proposition est de permettre la génération de messages à la compilation, comme ceux permis pour static_assert, pour les annotations [[deprecated]], [[nodiscard]] et les =delete.
On travaille sur le texte de la proposition. Entre autres, elle parle de string-like objects or l'idée de ces textes formatés à la compilation est de ne pas avoir de string object du tout, du moins au sens du mot object dans la machine abstraite.
Certains passages du texte de static_assert ont été déplacés pour fins de généralisation, mais on constate que cela a des conséquences (l'emplacement choisi visait à ne pas demander la production des messages si ce n'est pas nécessaire; la réorganisation ajoute du travail parfois inutile à la compilation, et la position du passage préexistant résultait de nombreuses heures de débat). On veut aussi limiter la portée de ces messages produits à la compilation dans le but d'éviter que ceux-ci soient observables par le programme.
Un enjeu intéressant est que dans le cas de static_assert, on sait clairement quel est le point de génération du texte dans un programme alors que pour une annotation, c'est beaucoup moins évident de cibler un point en particulier.
On examine la question de savoir si la substitution d'un message de diagnostic dans une annotation fait partie du immediate context. C'est subtil... On demande l'ajout d'une combinaison de [[nodiscard(diagnostic-message)]] avec std::source_location.
Il reste du travail à faire ici.
Cette proposition attaque un aspect du standard qui n'a jamais été clair, soit le comportement d'un programme lorsque survient un débordement sur un nombre à virgule flottante. On a par exemple des cas de comportement indéfini. Ce qu'il est possible de faire avec std::numeric_limits<T>::infinity() est un peu circonscrit (merci à SG6!), mais demande aussi un peu d'amour. Ce flou mène à de nombreuses divergences d'implémentation (le comportement vers lequel on souhaite converger est celui de gcc 15).
Le choix de mots est tellement délicat quand on parle de nombres à virgule flottante, de valeurs représentables, d'infinité... Une part importante des problèmes relevés sont préexistants à la proposition, et proviennent du texte du standard du langage C il y a des décennies, et on est tentés le les corriger mais cela accroîtrait beaucoup la portée de le proposition alors on cherche des solutions plus humbles, quitte à accepter qu'elles soient temporaires.
L'examen des changements proposés se fait de manière minutieuse. On remarque par exemple que dans le cas des expressions arithmétiques, le cas des conversions implicites résultant de recours à un opérateur de conversion comme X::operator float() n'est pas couvert. Aussi, dans les règles à appliquer, une partie du travail est commun aux entiers et aux nombres à virgule flottante mais il semble que la terminologie choisie introduirait (involontairement) du comportement indéfini sur certaines opérations applicables à des entiers non-signés.
Le texte proposé se promène entre le domaine des nombres réels (pour le formalisme théorique) et celui des nombres à virgule flottante (pour la représentation), ce qui pose des problemes en soi aussi car certains mots sont applicables à l'un mais pas vraiment à l'autre. Entre autres, le domains des réels est total sauf pour la division alors toutes les opérations autres que celles-ci résultent en des réels, ce qui ne correspond pas à ce qu'on cherche à décrire ici pour les flottants.
(il se fait tard; on poursuivra demain)
xxx
xxx
xxx
xxx
xxx
xxx
xxx
xxx
Après quelques irritants audio, Nina Ranns nous accueille.
ABI Group : pas de rapport à offrir.
Admin Group :
SG1: Concurrency and Parallelism Study Group :
SG2: Modules Study Group : inactif cette semaine
SG3: File System Study Group : inactif cette semaine
SG4: Networking Study Group : inactif cette semaine
SG5: Transactional Memory : inactif cette semaine
SG6: Numerics :
SG7: Reflection : inactif cette semaine
SG8: Concepts : inactif cette semaine
SG9: Ranges : inactif cette semaine
SG10: Feature Test : inactif cette semaine
SG12: Undefined and Unspecified Behavior : inactif cette semaine
SG13: I/O : inactif cette semaine
SG14: Low Latency : inactif cette semaine, mais on travaille chaque mois sur la proposition de Patrice Roy et sur les exceptions pour systèmes embarqués. Les interactions avec Networking et les exécuteurs nous occuperont au cours des prochains mois
SG15: Tooling : quelques travaux cette semaine
SG16: Unicode : inactif cette semaine mais on progresse
SG17: EWG Incubator : inactif cette semaine
SG18: LEWG Incubator :
SG19: Machine Learning : inactif cette semaine, mais on travaille chaque mois. Nos gros sujets sont les statistiques et le Machine Learning. Nous travaillons dans une acception plus globale de C++ et de l'intelligence artificielle contemporaine, C++ étant le substrat sur lequel le reste du monde de l'IA contemporaine est construit.
SG20: Education :
SG21: Contracts : inactif cette semaine
SG22: C/C++ Liason : inactif cette semaine
SG23: Safety and Security :
EWG :
LEWG :
CWG :
LWG :
Les votes amenés par CWG cette semaine sont les suivants.
Les votes amenés par LWG cette semaine sont les suivants.
Les votes pour WG21 sont les suivants.
Direction group : c'est un groupe avec direction rotative. Daveed Vandevoorde est Chair en ce moment.
Les prochaines rencontres seront à Búzios (Rio de Janeiro, Brésil), N5021, le 16 novembre 2026