Je participe à cette rencontre à distance. La rencontre de cette semaine se tenant à Wrocław, le décalage horaire sera rude, et j'ai des dossiers à défendre alors ce sera une semaine quelque peu... physique. Je ne serai que partiellement présente à cette rencontre alors soyez tolérantes et tolérants envers moi s.v.p.
Les principaux thèmes de la rencontre sont la progression des travaux pour C++ 26, en particulier la réflexivité statique et – surtout – les contrats. Je devrais passer ma semaine chez CWG comme à mon habitude, mais j'ai [[invalidate_dereferencing]] à défendre alors je vais investir du temps dans SG23.
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 :
La plénière se tiendra à 9h heure de Wrocław, donc 3h heure de Montréal, alors je devrai me lever très tôt. J'ai passé la fin de semaine à déménager ma fille Calypso qui est enceinte alors je commencerai la semaine un peu fatigué.
Au début de la plénière, John Spicer nous accueille et nos hôtes (Nokia) nous offrent quelques mots de bienvenue, rappelant que C++ est la fondation sur laquelle leurs produits reposent. C'est un bon discours qui va au-delà des bons mots et participe à la direction des travaux. Par la suite, John Spicer explique les règles de fonctionnement, le code de conduite, la mécanique des votes, etc. Nina Ranns prend ensuite la parole pour rappeler les règles de discussion (on est ici pour discuter « technique » et faire avancer C++, mais nous sommes aussi des humains passionnés alors ça doit se faire dans le respect). Elle introduit l'idée de « Rumble » qu'elle tire de la littérature et qui sied bien à nos travaux.
Le traditionnel tour de présentation des nouveaux membres suit. Quelques participants avec des intérêts particulièrement utiles (convergence entre le modèle mémoire ARM et celui de C++, par exemple, ou réseautique du point de vue de la programmation de routeurs). On a plusieurs invités, ce qui arrive souvent quand on est à un endroit où on va moins souvent (c'est chouette!)
On présente l'agenda de la semaine, puis on l'approuve comme à l'habitude, et on fait de même avec la version la plus récente du Draft du standard C++26 soit N4933. On signale aussi que C++23 en tant que standard officiel vient d'être publié (en 2024, mais c'est parce que la procédure chez ISO est un peu plus lourde que par le passé). La réflexivité statique sera un sujet chaud cette semaine alors on rappelle l'importance d'avoir un quorum fort lors des discussions à ce sujet. L'autre gros sujet cette semaine sera manifestement les contrats. Deux sessions de soirée sont prévues : perspectives sur les contrats ce soir, et Relocatability demain soir.
On se « déplace » ensuite vers les salles de travail. Je n'aurai qu'une brève présence aujourd'hui parce que... j'enseigne toute la journée!
Chez CWG, nous travaillerons beaucoup sur la réflexivité.
Il s'agit d'un « Late Paper » de St-Louis qui devrait être voté cette semaine. On le regarde rapidement... et il nous convient.
EWG a approuvé ceci à Varna, CWG à St-Louis, mais on souhaitait de l'expérience de la part des implémenteurs. Ce fut fait (bravo à Daveed Vandevoorde!). On fait le tour des exemples (c'est ce qui a changé, pour l'essentiel, depuis la R4), ce qui fait remarquer que l'expérience a de la valeur! Ça ira au vote samedi.
L'exemple motivateur est :
int x = 0;
alignas(int) std::byte y[sizeof(int)];
int z = *static_cast<int*>(std::memcpy(y, &x, sizeof(int)));
Selon [cstring.syn#3] on peut déduire que la valeur de retour de std::memcpy() retournerait ici un objet donnant au reste du programme un comportement bien défini. On semble d'accord que ce soit bel et bien le cas (notez que int est trivialement constructible).
L'exemple motivateur est :
[Example: When reusing storage denoted by some pointer value p, launder(reinterpret_cast
L'enjeu est que std::launder() est présenté comme prenant en paramètre un pointeur vers un objet qui est « within its lifetime ». On accepte la proposition d'ajustement de LWG pour que cet exemple devienne correct (il semble raisonnable)
Un exemple a été brisé récemment par une nouvelle proposition :
struct B {
bool operator==(const B&);
};
struct C : B {
C();
C(B);
bool operator!=(const B&);
};
bool c1 = B() == C(); // was OK, now ill-formed
Il semble que le souhait soit de corriger non pas le problème général, mais plutôt ce cas plus spécifique d'ambiguïté. Davis Herring pense qu'on a aussi brisé [namespace.udecl#11]. Le problème découle de notre manière de définir « corresponds » comme dans « Two functions have corresponding signatures if... », couplé aux règles de réécriture implicites de certains opérateurs (ici : operator==() sur la base de operator!=()). On se demande au passage si l'exemple est correct ou si B()!=C() serait plus près des intentions, mais on parle bel et bien de == dans ce cas. L'exemple original est [over.match,oper#4] :
struct A {};
template<typename T> bool operator==(A, T); // #1
bool a1 = 0 == A(); // OK, calls reversed #1
template<typename T> bool operator!=(A, T);
bool a2 = 0 == A(); // error, #1 is not a rewrite target
struct B {
bool operator==(const B&); // #2
};
struct C : B {
C();
C(B);
bool operator!=(const B&); // #3
};
bool c1 = B() == C(); // OK, calls #2; reversed #2 is not a candidate
// because search for operator!= in C finds #3
bool c2 = C() == B(); // error: ambiguous between #2 found when searching C and
// reversed #2 found when searching B
struct D {};
template<typename T> bool operator==(D, T); // #4
inline namespace N {
template<typename T> bool operator!=(D, T); // #5
}
bool d1 = 0 == D(); // OK, calls reversed #4; #5 does not forbid #4 as a rewrite target
Davis Herring mentionne (à juste titre) qu'il y a beaucoup de règles spéciales dans cette section. C'est l'heure de la pause pour le dîner; je ne serai probablement pas en mesure de participer à la suite des travaux car c'est l'heure de me préparer à aller donner mes cours.
(j'ai évidemment manqué beaucoup de trucs amusants durant « l'après-midi »... Particulièrement P1839R6 -- Accessing object representations et P2841R5 -- Concept and variable-template template-parameters)
Petit groupe ce matin chez CWG car il y a de la compétition cette semaine (contrats, réflexivité) et un de nos membres, Roger Orr, dirige SG23.
Des changements récents visant à rendre const_cast<int&&> ill-formed ont malencontreusement rendu certains trucs suspects bien formés : https://godbolt.org/z/qx9bqsz49 La résolution proposée semble bonne, mais il y a beaucoup de petits ajustements ici et là.
Le texte n'est pas clair quant à la possibilité (ou pas) de définir des membres d'une classe locale à une portée dans une autre portée. On clarifie
Le type de T(U&) ou T{ U& } peut parfois donner des résultats suspects. Il faut aussi clarifier que void(expr) ou void{ expr } est malformé
Ceci est intéressant :
static_assert(sizeof(bool) == 1); // assumption for the example
bool f() {
char c = 2;
bool b = true;
memcpy(&b, &c, 1); // #1
return b; // #2
}
Ici, b est initialement correct, le memcpy() crée un bool là où b se trouvait, mais le bool ainsi créé a une valeur incorrecte si on suppose static_cast<int>(true)==1 et static_cast<int>(false)==0. La proposition est de rendre ces représentations incorrectes UB.
Jason Merrrill rapporte des plaintes d'utilisateurs lorsque les compilateurs optimisent le code sur la base de valeurs hors bornes. Cela dit, on y va avec ce qui est proposé.
Ceci vise à clarifier ce qui se passe quand on fait certaines conversions entre des entiers de signes mixtes (affectation, operator++(), ce genre de truc), particulièrement lire une valeur int et la placer dans un objet unsigned int. Le terme choisi semble être « ...value congruent to V », un terme qui vient de [basic.fundamental]
La proposition est de rendre ceci valide même en l'absence du mot template (qui éviterait l'ambiguïté grammaticale avec operator<()) sur les noms de types, car on se trouve dans un type-only context :
template<typename T> class C {
T::X<int> g1(); // #1
T::X<int>::Y g2(); // #2
};
template<typename T>
T::X<int> h1(); // #3
template<typename T>
T::X<int>::Y h2(); // #4
On suggère d'étendre l'idée type-only context et d'ajouter celle de transitively type-only context pour couvrir ces cas. Sans cet ajustement, #1 et #2 sont valides, mais #3 et #4 ne le sont pas ce qui semble incorrect. Le travail ici est de valider que les changements proposés n'acceptent, grammaticalement, que ce qui devrait être accepté, et pas plus. C'est délicat car on parle du contexte dans lequel le traitement du texte du code source se trouve au moment où le symbole < apparait dans le texte (on ne veut pas accepter silencieusement ce symbole à un endroit où il n'est pas permis). On essaie aussi de voir si on peut s'en sortir sans introduire un nouveau terme grammatical.
On hésite à l'accepter telle quelle, mais il se trouve que la proposition vient de Richard Smith qui l'a effectivement implémentée dans clang... Jason Merrill rappelle que la proposition semble très étrange du point de vue d'un processeur de langage, qui doit prendre une décision en rencontrant un symbole dans un monde idéal, On la retravaillera...
La proposition met de l'avant que ce qui suit n'est pas value-dependent mais devrait l'être car les constructeurs de T pourraient lever une exception :
template<typename T>
void f() {
noexcept(static_cast<int>(T{}));
noexcept(typeid(*T{}));
noexcept(delete T{});
}
Les changements requis aux productions grammaticales ne sont pas banals. Jonathan Caves fait remarquer que les braced-init-lists sont incorrectement traités par la proposition. On fouille, on réfléchit... Jens Maurer suggère qu'une expression est value-dependent si un de ses éléments est value-dependent, ce qui semble prometteur. On travaille le texte... Ça semble correct, mais c'est un peu verbeux. On le prend quand même
L'exemple sous examen est :
struct S {};
S a;
constexpr S b = a; // OK, call to implicitly-declared copy constructor
constexpr S d = false ? S{} : a; // error: lvalue-to-rvalue conversion of 'a' is not a constant expression
Il y a divergence d'implémentation chez les compilateurs. Ceci est un symptôme d'un problème plus grave (avec les templates variadiques) mais on convient de le régler séparément pour le moment. Les divergences tiennent du fait que l'algorithme qui décrit dans quel ordre les conversions sont faites est imparfait. On débat de la pertinence de faire quelques « drive-by fixes » au passage...
Ceci est ill-formed, bien que l'on ne lise jamais la valeur de np :
void f() {
std::nullptr_t np; // uninitialized, thus np contains an erroneous value
constexpr void *p1 = np; // error: converted initializer is not a constant expression
}
L'ajustement requis semble mineur. On procède.
On souligne la différence entre nombre « physique » de lignes et nombre « logique » de lignes, et ceci fait une différence par exemple dans le cas de macros dont les lignes se terminent par « \ ». La démonstration du problème est... amusante :) On accepte la résolution
Avec le texte existant, on a :
struct S {};
int main() {
constexpr S s; // OK
constexpr S s2 = s; // error: s is not constant-initialized
}
... ce qui ne semble pas respecter l'intention. La raison est que le texte dans constexpr utilise « ...shall be initialized... », ce qui n'est pas le cas de s ici qui n'a pas « une sorte d'initialisation ». On pense s'en sortir en ajoutant la possibilité que le type soit const-default-constructible [dcl.init.general]
L'exemple motivateur est :
bool f() {
constexpr int z = 42;
return requires {
sizeof(int [*&z]);
} && requires (int x) {
sizeof(int [*&z]);
};
}
L'enjeu est que le deuxième requires introduit un « function parameter scope » et reçoit un traitement différent du premier avec le texte existant, ce qui ne semble pas raisonnable. On convient que les deux devraient avoir le même traitement. Il y a divergence d'implémentation, manifestement
(brève pause à 4 h 15 heure de Montréal)
Après la pause, Jens Maurer nous informe avoir « rebasé » le texte de la proposition sur la plus récente version du standard, ce qui simplifie les ajustements requis. On l'accepte
Le terme « appears within » est saupoudré un peu partout (par exemple, dans sizeof) mais n'est pas formellement défini. On le fait disparaître en reformulant certains passages. C'est plus clair, bravo.
Une bête erreur. On déplace le terme au bon endroit. Oups.
Le texte permet ceci :
struct A {
void f(this void);
};
Heureusement, ça se corrige aisément
On a des cas d'ambiguïté qu'on aimerait résoudre, soit ceux-ci (impactés par les « trailing requires clauses ») :
template<bool B> struct X {
void f(short) requires B;
void f(long);
template<typename> void g(short) requires B;
template<typename> void g(long);
};
void test(X<true> x) {
x.f(0); // #1, ambiguous
x.g<int>(0); // #2, ambiguous
&X<true>::f; // #3, OK!
&X<true>::g<int>; // #4, ambiguous
}
... de même que (ceux-ci sont impactés par auto) :
template<bool B> struct X {
void f(short) requires B;
void f(short);
template<typename> void g(short) requires B;
template<typename> void g(short);
};
void test(X<true> x) {
auto r = &X<true>::f; // #5
auto s = &X<true>::g<int>; // #6
}
On introduit un nouveau terme, partial-ordering-constrained, et un définit un ordre selon lequel une fonction peut l'être plus qu'une autre (avec les règles requises, évidemment). Avec les ajustements, on arrive à :
template<bool B> struct X {
void f(short) requires B; // #1
void f(short); // #2
};
void test() {
auto x = &X<true>::f; // OK, deduces void(*)(short), selects #1
auto y = &X=<false>::f; // OK, deduces void(*)(short), selects #2
}
... ce qui est satisfaisant! On travaille le texte proposé pour qu'il soit plus clair. On note que ce changement brise du code qui était correct (mais mal avisé) avec C++23, par exemple :
template <typename T>
void f(T &&, void (*)(T &&));
void g(int &);
inline namespace A {
void g(short &&);
}
inline namespace B {
void g(short &&);
}
void q() {
int x;
f(x, g); // ill-formed; previously well-formed, deducing T=int&
}
... mais ne faites pas ça!
Il existe des divergences d'implémentation avec :
struct A {
A(const A&) = delete;
};
struct B {
operator A&&();
};
const A& r = B();
On prend un peu de temps pour analyser les enjeux. Les mots choisis pour la résolution demandent un peu d'ajustements (p. ex. : on convertit vers un type, pas vers une valeur). Il n'est pas simple de s'entendre sur ce qu'on estime valide ici, mais on finit par s'entendre et celle-ci est prête.
Ceci est ill-formed selon les règles actuelles :
export module mod;
extern "C++" void func();
export extern "C++" {
void func();
}
... mais il n'est pas clair pourquoi dans le cas du module global. Les mêmes règles brisent ceci :
export module M;
namespace N { // external linkage, attached to global module, not exported
void f();
}
namespace N { // error: exported namespace, redeclares non-exported namespace
export void g();
}
... qui (selon l'intention originale) aurait dû être correct. On envisage conserver l'interdit seulement dans le cas d'une entité attachée à un module nommé, mais il y a des discussions quant à la validité de cette approche.
(durant l'après-midi, des travaux sur les contrats ont eu lieu chez CWG; gros sujet! Suite demain...)
Je manque les cinq premières minutes mais me voilà.
Alisdair Meredith propose de supprimer les comparaisons comme ==, < ou >= sur les tableaux bruts. Elle subissent la décrépitude de pointeurs en C, sont dépréciées en C++ depuis C++20 et ceci serait le dernier « clou dans le cercueil ».
Hubert Tong demande comment les comparaisons avec std::nullptr_t sont couvertes. Alisdair Meredith explique que l'intention est d'appliquer les mêmes règles que celles comparant avec un pointeur tout simplement.
Il demeure possible de comparer des adresses. Par exemple :
int a0[5];
int a1[5];
a0 == a1; // était bien formé, désormais ill-formed
a0 == +a1; // tableau et pointeur, comparaison d'adresses
a0 < +a1; // comparaison d'adresses, résultat unspecified
Évidemment, ceci introduit une différence entre C et C++, mais je suppose que C suivra l'exemple sous peu.
On est rendus là, après près de vingt ans de travail. Pour un résumé, on a :
int f(const int x)
pre (x != 1) // a precondition assertion
post(r : r != 2) // a postcondition assertion; r names the result object of f
{
contract_assert (x != 3); // an assertion statement
return x;
}
... ce qui aurait pour conséquence :
void g()
{
f(0); // no contract violation
f(1); // violates precondition assertion of f
f(2); // violates postcondition assertion of f
f(3); // violates assertion statement within f
f(4); // no contract violation
}
Quatre sémantiques sont possibles : ignore, observe, enforce, quick_enforce. La sémantique choisie à la compilation influence le comportement lors d'un bris de contrat.
On s'assure qu'il y ait un Feature Test Macro. On discute ensuite du positionnement : les contrats sont probablement trop petits pour être un chapitre du standard, mais ils couvrent très « large » et il est difficile de voir dans quelle section existante on pourrait les placer. Le coeur sera probablement dans [basic.contract]. Notez qu'on ne peut pas utiliser les mécanismes comme va_start dans un contrat, et tant mieux!
Dès le début, Jason Merrrill signale qu'on présente les contrats de manière incorrecte : le texte dit « Contract assertions allow the programmer to specify states of the program that are considered incorrect at certain points in the program execution. » mais c'est l'inverse qu'on fait dans un contrat : on indique ce à quoi on s'attend! Les auteurs apprécient l'idée, heureusement.
Ce qui est utilisé de manière nommée dans un contrat est considéré const. Bonne chose selon moi. On peut contourner cela, bien sûr, mais c'est le comportement par défaut. Il y a des réactions (c'est un sujet controversé) mais Jens Maurer rappelle les gens à l'ordre en indiquant que les décisions de design se prennent dans une autre pièce que celle où siège CWG.
On discute de questions de fond (c'est une proposition de près de 100 pages). Par exemple, que signifie « évaluer » un contrat? Évalue-t-on le contrat ou les clauses qui y apparaissent? EWG a demandé formellement que des time-travelling-optimizations ne soient pas permises dans le contexte de contrats alors le texte introduit des observable checkpoints [intro.abstract] ce qui fait grincer quelques dents. On note que les prédicats peuvent être évalués zéro, une ou plusieurs fois (oui, l'élision de prédicats est permise) alors il y a un risque documenté à implémenter des prédicats ayant des effets de bord, mais on ne parle pas de UB ici. On demande un exemple qui montre les risques d'un prédicat avec effet de bord, par exemple inspiré de celui-ci (correct, mais qui démontre autre chose) :
constexpr int f(int i)
{
contract_assert((++const_cast<int&>(i), true));
return i;
}
inline void g()
{
int a[f(1)]; // size dependent on the evaluation semantic of contract_assert above
}
(notez que cet exemple peut provoquer une violation d'ODR!)
On a un enjeu avec l'extensibilité planifiée de l'ensemble des sémantiques permises. Jens Maurer signale la difficulté pour une programmeuse ou un programmeur d'exprimer un programme correct et portable dans les circonstances. Bon point. Les auteurs sont en désaccord, leur proposition ayant été pensée pour cette ouverture. On discute de la possibilité d'avoir un ensemble fermé pour le Core Language et ouvert pour la bibliothèque standard. La direction choisie est de ne pas ouvrir la porte aux extensions : en C++, il n'existe pas de Conforming Extensions de toute manière, alors le standard n'a pas à les mentionner; un compilateur peut offrir des extensions et ça ne concerne pas le standard
L'évaluation d'un prédicat dans un contrat peut produire la valeur true, la valeur false, lever une exception ou ne pas produire de résultat (appeler longjmp(), terminate() ou autre chose du genre). Il faut expliquer ce que chaque cas aura comme effet. Au passage, on rappelle que « shall not » est une formule plus forte que « ill-formed » car « ill-formed » oblige le compilateur à émettre au moins un diagnostic, mais ne l'empêche pas de poursuivre les opérations (par exemple, lorsqu'une extension est rencontrée). Avec shall not, le programme ne peut simplement pas compiler (c'est ce qui ce produit dans un static_assert(B) où B évalue à false).
Des préoccupations sont soulevées quant à l'implémentabilité de certaines propositions (sur la base du support d'extensions comme les Variable-Length Arrays). Les auteurs pensent que c'est le cas. Hubert Tong demande un lien direct pour examiner le tout. Joshua Berne offre https://godbolt.org/z/qEo1vGhqM et https://godbolt.org/z/qEo1vGhqM
(brève pause vers 4 h 15 heure de Montréal)
L'ordre dans lequel un std::contract_violation_handler est construit et détruit dans le contexte de la violation du contrat est décrit de manière à permettre de déterminer ce qui peut être fait par un tel objet (par exemple, on définit que l'exception levée -- si c'est le cas -- est observable dans le std::contract_violation_handler).
Hubert Tong propose l'exemple suivant :
int glob = 1;
constexpr int g() {
//contract_assert(false);
contract_assert(glob);
return 1;
}
void f() {
int arr[g()];
}
On a ici un cas de manifestly constant evaluated qui n'est pas un core constant expression, alors ça ne semble pas couvert par le texte. En plus, on a un variable length array (extension) si l'assertion échoue et un tableau sinon. Brrr...
(beaucoup de travaux s'ensuivent mais ça se raconte mal ici; je dois fermer les yeux vers 5 h 30 pour une heure environ sinon je ne passerai pas la journée)
Je dois présenter p3442r0, [[invalidate_dereferencing]], à 10 h 30 heure locale (4 h 30 heure de Montréal). J'arrive à SG23 à 2 h 40 du matin (heure de Montréal) alors que Herb Sutter est en train de présenter (j'étais, bêtement, dans la douche... C'est difficile de dormir si peu toute la semaine)
Frank Birbacher rapporte que les faux-positifs peuvent causer de réels dégats en pratique. Herb Sutter explique sa perspective sur le sujet. parlant d'action locale et de faux-positifs possibles mais rares
Herb Sutter explique des sémantiques qu'il nomme "Fix" et "Reject". Il explique, avec un static_cast<Enfant*>(ptrParent), que si le compilateur constate que ptrParent a une vtbl et si RTTI est activé, la sémantique "Fix" pourrait le remplacer par un dynamic_cast et la sémantique "Reject" rejetterait le code. Il dit que l'intention est de ne pas changer le sens du code, sauf dans le cas où le code original serait UB. Il mentionne qu'une partie de la solution serait d'améliorer la vitesse de dynamic_cast. Une autre sémantique serait "Check" qui ajouterait des tests, par exemple valider les bornes d'un accès à un type à travers obj[i] pour lequel std::size() existe. Enfin, il suggère qu'on pourrait offrir "Modernize" où le compilateur offrirait un correctif possible (peut-être celui que "Fix" aurait pu faire)
On discute des réactions possibles à une violation de profil, mais on souligne qu'il s'agit d'un sujet quelque peu orthogonal au propos. Roger Orr souligne que pour un template, émettre des bons diagnostics peut être ardu car une opération peut être valide pour certains types et moins pour d'autres
Herb Sutter rapporte avoir implémenté le test implicite des bornes dans Cpp2 (injectant 0<=i&&i<size(c) ou ssize(c) selon les cas) pour tous les accès à des conteneurs contigus en mémoire dans les cas où les Bound Safety Profiles sont activés. Il insiste sur l'intérêt de ne pas avoir à modifier le code pour avoir les bénéfices, et rapporte avoir trouvé des erreurs dans son propre code. Son implémentation rapporte les erreurs à travers std::source_location, ça donne de bons diagnostics. Son implémentation utillise les contrats, ce qui rend les diagnostics personnalisables à travers des contract_violation_handler
Herb Sutter explore l'impact de l'introduction de profils en distinguant les pratiques dans du code existant (idéalement, on recompile et c'est tout) et dans du nouveau code (où un changement aux sources est une possibilité). Il met de l'avant le fait que les législateurs qui insistent sur un accroissement de la sécurité dans les langages de programmation commencent à nous donner des échéances, alors une adoption rapide de certains profils permettrait aux gens qui souhaitent « montrer patte blanche » de répondre « je recompilerai mon code avec C++26 » pour devenir conformes (ceci s'ajoute aux autres efforts de C++26 comme le Erroneous Behavior par exemple)
Herb Sutter propose (sans demander qu'on le standardise) des niveaux d'adoption : suppress(P) pour supprimer un profil dans la compilation d'une unité de traduction, apply(P) pour l'appliquer avec "Fix" + "Check" + "Modernize", et enforce(P) pour imposer la sémantique "Reject". Il rapporte ensuite des votes pris en SG15 cette semaine et montrant que ce groupe a reçu favorablement les idées de P3081, et indique que plusieurs vendeurs de compilateurs font déjà ce qui est demandé, en tout ou en partie
On discute de l'urgence : si on veut qu'une telle proposition fasse partie de C++26, il faut qu'elle soit vue par EWG cette semaine, donc... demain. Frank Birbacher mentionne qu'il envisage des frictions possibles dans le cas de "Modernize" avec les gens de SG15. Herb Sutter explique avoir parlé à SG15 plus tôt cette semaine et avoir abordé cette question
On demande quelle sera l'expérience usager avec du code transformé par "Modernize", où le code parcouru est différent du code écrit. L'argument de Herb Sutter est qu'on parle ici de changer du code UB en code qui ne l'est pas, ce qui est permis déjà!
On prend un vote et il est favorable, alors cette proposition sera amenée devant EWG demain.
J'ai d'abord expliqué que cette proposition s'inscrit dans la démarche générale de P2966, qui collige des demandes provenant principalement du monde des programmeuses et des programmeurs de jeux vidéos, mais qui est portée par SG14 et l'ensemble du monde du développement des systèmes à basse latence.
Ma proposition était évidemment plus humble que celle de Herb Sutter, étant très ciblée et visant principalement à porter l'intention des programmeuses et des programmeurs exposant des fonctions qui acceptent des paramètres modélisant des indirections (pointeurs, références, pointeurs intelligents, etc.) et sont susceptibles d'invalider les pointés ou les référés. J'ai montré des exemples d'utilisation, décrit les effets atttendus, abordé les cas plus subtils comme les fonctions qui invalident potentiellement un pointé sur une branche mais pas sur l'autre, etc.
Des questions pertinentes ont suivi. Il y a de l'appui pour la proposition, de la résistance aussi : on se demande quels sont les diagnostics souhaités, surtout dans les cas où l'invalidation n'est que potentielle; on se demande comment P3442 interagira avec la proposition P3465 de Herb Sutter sur la durée de vie des objets (certains estiment que P3465 est un ensemble qui englobe en quelque sorte P3442, d'autres voient les deux propositions comme complémentaires); on se demande si on devrait étendre les diagnostics à des fonctions comme unique_ptr<T>::get(); etc.
Je pense avoir bien préparé le terrain : j'avais prévu les questions, relativement bien couvert les enjeux (on peut réévaluer les solutions proposées, mais les problèmes qu'on cherche à solutionner semblaient bien cernés). Après une intéressante discussion d'une quarantaine de minutes, nous avons tenu un vote et ce vote fut favorable à majorité, avec quelques « contre ». Les « contre » ont exprimé ce qui permettrait de les rendre plus réceptifs à P3442 dans sa version R1 alors nous savons ce qui doit être raffiné.
Ce fut productif!
Je ne peux pas participer au reste des travaux aujourd'hui pour des raisons familiales, alors suite demain.
J'arrive avec une dizaine de minutes de retard chez CWG.
Il semble que les exemples qui appuyaient la partie « Annexe C » sont disparus de la plus récente version du texte. On les réintègre.
Jan Schultke présente. L'idée est d'interdire une ellipse dans une macro sans qu'elle ne soit précédée par une virgule (pour éviter ces étranges cas où on peut rencontrer ...... -- oui, six petits points -- que même C ne supporte pas). C'est un tout petit changement aux productions grammaticales.
On élague un peu les exemples, retirant les mentions que void f(int...) est illégal (vrai mais redondant) mais conservant le fait que void f(auto...) est illégal. Chez CWG, le choix des exemples est très, très important. On reverra la proposition une fois les changements faits
On fait le tour des changements termninologiques. Comme on pourrait s'y attendre, l'idée est que l'on peut évaluer une coroutine à la compilation, mais qu'il n'y a pas d'interaction entre le monde compile-time et le monde run-time. On passe pas mal de temps sur deux lignes qui semblent simples mais peuvent demander un effort de mise à jour vraiment non-trivial chez nos compilateurs.
Conséquence de cette proposition : on disait auparavant « une fonction est constexpr-suitable si... » mais il ne reste presque plus de cas dans la liste, alors on passe à « une fonction est constexpr-suitable sauf si... »
Le travail est fait. On l'amène pour vote demain.
Il reste de petits détails qui accrochent dans la présentation.
(c'est un matin étrange : on a beaucoup de gros dossiers, mais les auteurs ou les contributeurs principaux sont tous retenus ailleurs)
L'enjeu des ressources requises pour implémenter ceci revient sur la table. On le veut, mais à peu près personne ne semble avoir les ressources pour le faire. Hum... L'implémentation de référence repose sur des fibres, apparemment, mais ce n'est pas une option sur tous les compilateurs. On débat longtemps de ce qui est raisonnablement faisable ici. On va renvoyer la proposition à EWG pour réévaluation dans les circonstances
On a travaillé plusieurs fois sur l'implémentation de cette idée à travers l'injection de « templated regions » dans du code non-générique, mais cette semaine EWG a demandé de limiter le mécanisme à des fonctions génériques, ce qui simplifie le modèle. On pourra par exemple avoir ceci :
struct C { int x, y, z; };
template <class T>
void now_i_know_my() {
auto [a, b, c] = C(); // OK, SB0 is a, SB1 is b, and SB2 is c
auto [d, ...e] = C(); // OK, SB0 is d, the pack e (v1) contains two structured bindings: SB1 and SB2
auto [...f, g] = C(); // OK, the pack f (v0) contains two structured bindings: SB0 and SB1, and SB2 is g
auto [h, i, j, ...k] = C(); // OK, the pack k is empty
auto [l, m, n, o, ...p] = C(); // error: structured binding size is too small
}
... de même que cela :
struct C { };
void g(...); // #1
template <typename T>
void f() {
C arr[1];
auto [...e] = arr;
g(e...); // calls #2
}
void g(C); // #2
int main() {
f<int>();
}
On amène ça au vote demain.
(brève pause du matin)
La proposition est de supprimer des comportements qui ont été dépréciés lors de versions antérieures du langage, essentiellement des opérations RMW (read-modify-write) comme operator++, mais aussi dans la déclaration de Structured Bindings par exemple. Il se trouve que bien écrire les changements attendus est plus subtil qu'il n'y paraît... L'un des enjeux est que le texte propose de faire en sorte qu'une version « discarded » de l'expression serait de type void, mais on remarque que cela rendrait l'utilisation de sizeof sur l'expression deviendrait ill-formed... On fait un léger changement aux mots et on informe EWG pour s'assurer qu'ils sont à l'aise avec ce changement.
(je dois quitter vers 5 h 30 du matin; je m'endors et je ne suis pas fonctionnel)
Après une sieste (nécessaire!) et une rencontre de parents à l'école de mon plus jeune Ludo, j'ai pris un peu de temps pour relire les travaux de CWG auxquels je n'ai pas pu participer cette semaine (le jour, heure de Montréal, j'enseignais la plupart du temps).
La plénière débute vers 2 h 30 du matin heure de Montréal, 8 h 30 heure de Wrocław.
SG1: Concurrency and Parallelism Study Group : bonnes rencontres, un peu plus de vingt propositions. Deux faits saillants : les Concurrent Queues pourraient faire leur entrée avec C++26 et seraient un excellent outil d'enseignement pour les Senders / Receivers (ce sera, l'air de rien, notre première structure de données concurrente standardisée!), et... on a supprimé std::memory_order_consume. À l'unanimité! On manque nettement de scribes, ce qui nous a ralenti (ceci semble être un problème dans d'autres groupes)
SG2: Modules Study Group : inactif
SG3: File System Study Group : inactif
SG4: Networking Study Group : pas de rencontre cette semaine, mais on s'attend bientôt à une proposition formelle suivant des discussions qui se sont tenues à Tokyo
SG5: Transactional Memory : inactif
SG6: Numerics : on était peu nombreux mais on avait quorum. On a entre autres clarifié nos attentes en lien avec les « extended floating point types », et nous avons passé beaucoup de temps sur la compatibilité entre C++ et C23 en ce qui a trait aux bibliothèques mathématiques
SG7: Reflection : nous avons vu beaucoup de propositions, la plus importante étant celle discutant de la syntaxe de la réflexivité (qui a un peu changé). Nous avons décidé que nous voulois accéder les membres privés des classes. Nous pensons apporter la réflexivité pour vote à la prochaine plénière
SG8: Concepts : inactif
SG9: Ranges : beaucoup de travail cette semaine, incluant du temps accordé à SIMD
SG10: Feature Test : rien à dire cette semaine
SG12: Undefined and Unspecified Behavior : rien à dire cette semaine
SG13: I/O : inactif
SG14: Low Latency : pas de rencontre cette semaine
SG15: Tooling : quelques rencontres, le plus important sujet était le Ecosystem IS. Nous avons aussi examiné les contrats et les Safety Profiles
SG16: Unicode : pas de rencontre cette semaine
SG17: EWG Incubator : plusieurs propositions. On fait un effort pédagogique pour aider les gens à mieux présenter leurs idées
SG18: LEWG Incubator : nous avons passé le plus clair de notre temps sur trois propositions relativement importantes, réparties sur deux jours
SG19: Machine Learning : pas de rencontre cette semaine
SG20: Education : on travaille principalement sur des Teaching Guidelines en vue de l'enseignement de divers sujets rattachés à C++. N'hésitez pas à nous envoyer des propositions directement
SG21: Contracts : nous devions nous rencontrer deux fois cette semaine, mais l'une des deux rencontres s'est passée chez CWG car EWG a décidé de nous envoyer travailler sur la terminologie. Ça avance bien! Des enjeux ont été relevés quant aux préconditions et aux postconditions sur les constructeurs et les destructeurs. On devra unifier les contrats et le Erroneous Behavior, et on souhaite clarifier l'idée d'une exception s'échappant d'une fonction noexcept représentée par une violation de contrat
SG22: C/C++ Liason : pas de rencontre cette semaine
SG23: Safety and Security : nous avons eu beaucoup de propositions et nous avons tenu deux jours et demie de rencontre. Un enjeu important est d'empêcher les Time Travel Optimizations à travers les accès à des variables qualifiées volatile. Nous avons examiné le Standard Library Hardening et lui avons donné un vote favorable, nous avons examiné les Safety Profiles, la proposition pour un Safe C++ qui a été populaire (mais tristement, l'auteur ne semble plus en mesure de poursuivre ses travaux). Le groupe a discuté des directions à privilégier pour le futur (profils, Safe C++, les deux) mais n'a pas statué fermement
ABI Group : pas de rencontre cette semaine
Admin Group : pas de rencontre, mais rappelle les dates pour les prochaines publications
Herb Sutter présente les grandes lignes : 21 National Bodies, 220 personnes environ, dont les deux tiers en personne. On a un rappel de l'importance d'éviter les médias sociaux avant la fin de la plénière car on ne veut pas donner l'impression que les discussions sont influencées par des tiers externes
EWG : nous nous sommes rencontrés toute la semaine durant. Nous nous sommes concentrés sur C++26, qui sera Feature Complete à la prochaine rencontre, et nous avons vu pas moins de 50 propositions. Nous pensons qu'il est possible d'avoir les contrats pour C++26 si les gens travaillent ensemble pour réduire les frictions, et nous avons donné de la rétroaction en ce sens. La réflexivité utilisera désormais l'opérateur ^^ qu'on nommera le « unibrow operator » :) (il y a des tas d'autres trucs importants cette semaine). On a travaillé sur le Pattern Matching, même si certains sont d'avis que ça ne sera pas prêt pour C++26 (nous y croyons!). On a eu une présentation dans P3466 sur les principes de C++ et on en fera un Standing Document (il y a plusieurs réactions à ceci, et c'est intéressant). Une discussion s'ensuit sur ce que nous pouvons humainement produire en tant que wording pour C++26 (on a des craintes de ne pas pouvoir livrer certains trucs car les contrats, la réflexivité et le Pattern Matching sont tous les trois très massifs)
LEWG : nous avons envoyé plus de 25 propositions à LWG sur la trentaine que nous avons examiné. Le processus de LEWG pour les votes électroniques et la priorisation des propositions est expliqué. Les gros morceaux cette semaine furent les contrats (approuvés!), la Trivial Relocatability (C++26 probablement!), SIMD, la réflexivité, les senders / receivers, mais il y a eu beaucoup de trucs intéressants outre cela
CWG : nous nous sommes rencontrés toute la semaine. Les principaux dossiers fiurent la réflexivité et les contrats. Les ajustements votés par EWG sur ces dossiers compliquent le travail de CWG, alors il faudrait se limiter à des ajouts dans le futur. On respecte les priorités fixées par WG21, mais on a tout de même résolu 31 Core Issues. L'enjeu de l'implémentabilité des constexpr coroutines avec les ressources disponibles est expliqué (c'est revenu à EWG pour un réexamen sur la base de cette information)
LWG : nous avons été occupés! Faits saillants : SIMD pour C++26, après plus d'un an de travail (bravo à LEWG qui a été très flexible et nous a aidé à progresser); indirect et polymorphic, des types qui aideront plusieurs programmeuses et programmeurs; std::hive est presque prêt (enfin!)
Les votes amenés par CWG cette semaine sont les suivants.
1. Accept as Defect Reports and apply the proposed resolutions of all issues in P3524R0 (Core Language Working Group "ready" Issues for the November, 2024 meeting) to the C++ Working Paper.
Unanime
2. Apply the changes in P3340R0 (A Consistent Grammar for Sequences) to the C++ Working Paper.
Unanime
3. Apply the changes in P2686R5 (constexpr structured bindings and references to constexpr variables) to the C++ Working Paper.
Unanime
4. Apply the changes in P3068R6 (Allowing exception throwing in constant-evaluation) to the C++ Working Paper.
Unanime (avec applaudissements!)
5. Apply the changes in P3247R2 (Deprecate the notion of trivial types) to the C++ Working Paper.
Unanime
6. Apply the changes in P2865R6 (Remove Deprecated Array Comparisons from C++26) to the C++ Working Paper.
Unanime
7. Apply the changes in P1061R10 (Structured Bindings can introduce a Pack) to the C++ Working Paper.
Unanime
8. Apply the changes in P3176R1 (The Oxford variadic comma) to the C++ Working Paper.
Vote demandé. Consensus en faveur
9. Accept as a Defect Report and apply the changes in
P3422R1 (Allow main function in named modules) to the C++ Working Paper.
Ce dernier vote a été retiré au cours de la nuit car « new information has come up », apparemment.
Les votes amenés par LWG cette semaine sont les suivants.
1. Apply the changes for all Ready and Tentatively Ready issues in P3504R0 (C++ Standard Library Ready Issues to be moved in Wrocław, Nov. 2024) to the C++ working paper.
Unanime
2. Apply the changes in P3136R1 (Retiring niebloids) to the C++ working paper.
Vote demandé. Consensus en faveur
3. Apply the changes in P3138R5 (views::cache_latest) to the C++ working paper.
Unanime
4. Apply the changes in P3379R0 (Constrain std::expected equality operators) to the C++ working paper.
Unanime
5. Apply the changes in P0472R2 (Put std::monostate in utility) to the C++ working paper.
Unanime
6. Apply the changes in P2862R1 (text_encoding::name() should never return null values) to the C++ working paper.
Unanime
7. Apply the changes in P2897R7 (aligned_accessor: An mdspan accessor expressing pointer over-alignment) to the C++ working paper.
Unanime
8. Apply the changes in P3355R1 (Fix submdspan for C++26) to the C++ working paper.
Unanime
9. Apply the changes in P3222R0 (Fix C++26 by adding transposed special cases for P2642 layouts) to the C++ working paper.
Unanime
10. Apply the changes in P3050R2 (Fix C++26 by optimizing linalg::conjugated for noncomplex value types) to the C++ working paper.
Unanime
11. Apply the changes in P3396R1 (std::execution wording fixes) to the C++ working paper.
Unanime
12. Apply the changes in P2835R7 (Expose std::atomic_ref's object address) to the C++ working paper.
Vote demandé. Consensus, mais avec beaucoup d'abstentions et les « contre » sont des gens particulièrement « senior ». On consulte les NB. Il est possible qu'on revisite (l'enjeu est le type de retour qui est T* alors que certains sont d'avis que void* serait préférable)
13. Apply the changes in P3323R1 (cv-qualified types in atomic and atomic_ref) to the C++ working paper.
Unanime
14. Apply the changes in P3508R0 (Wording for "constexpr for specialized memory algorithms") and P3369R0 (constexpr for uninitialized_default_construct) to the C++ working paper.
Unanime (merveilleux pour les gens avec qui je travaille, d'ailleurs!)
15. Apply the changes in P3370R1 (Add new library headers from C23) to the C++ working paper.
Unanime
16. Apply the changes in P3309R3 (constexpr atomic and atomic_ref) to the C++ working paper.
Unanime
17. Apply the changes in P3019R11 (indirect and polymorphic: Vocabulary Types for Composite Class Design) to the C++ working paper.
Unanime
18. Apply the changes in P1928R15 (std::simd — merge data-parallel types from the Parallelism TS 2) to the C++ working paper.
Vote demandé. Consensus (et applaudissements nourris!)
19. Apply the changes in P3325R5 (A Utility for Creating Execution Environments) to the C++ working paper.
Unanime
(pause demandée, ce fut un long processus)
Direction Group : nous avons tenu une brève rencontre mercredi. Nous sommes d'avis qu'un progrès rapide sur le plan du Safety est essentiel, et par « rapide » on parle de C++26. Nous offrons entre autres un encouragement aux travaux sur les contrats. Nous encourageons les groupes de travail à examiner des mécanismes moins ciblés, et à penser à comment leurs idées pourront être enseignées. Nous estimons que le Pattern Matching est important. Nous encourageons les gens à maintenir un ton et un choix de mots respectueux dans leurs échanges.
Sur le plan de la mécanique, on pense qu'il est temps de migrer vers un système de Wiki avec contrôle individuel. Le mot de passe du Wiki va changer d'ici la rencontre de Hagenberg en février.
Les prochaines rencontres sont à Hagenberg (Autriche), à Sofia (Bulgarie) et à Kona (Hawaii). Nos hôtes de Hagenberg nous offrent quelques mots, et nous expliquent que non, on ne peut pas skier à Hagenberg :) On cherche des hôtes pour 2026
Après quelques technicalités, on ajourne la séance et on met fin à cette productive semaine de travail. Il est 5 h du matin à Montréal et c'est l'heure pour moi de dormir un peu...