À propos de WG21, Tokyo 2024 (mars)

Je participe à cette rencontre à distance (elle se passe à Tokyo, mais ce n'était pas dans mes moyens cette année; j'aurais aimé y aller et amener mon amoureuse et mes enfants les plus jeunes... on se reprendra!). Merci à mon supérieur immédiat au Collège Lionel-Groulx, Patrick Lebonnois, pour avoir respecté notre entente de longue date et m'avoir laissé m'absenter de classe avec salaire pour une semaine (j'ai évidemment donné de quoi à faire à mes chouettes étudiant(e)s!). Je vais essayer d'offrir au moins en partie mes prestations à l'Université de Sherbrooke, celles-ci tombant soit juste avant, soit juste après les journées de travail au comité.

La rencontre de cette semaine se tenant à Tokyo, le décalage horaire est rude (13 heures), ce qui fait que les « journées » débutent à 19 h 30 heure de Montréal (20 h le premier soir, dimanche au Québec, ce qui correspond à 9 h à Tokyo).

Les principaux thèmes de la rencontre sont la progression des travaux pour C++ 26.

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 :

Jour 0 – 18 mars 2024

La préparation à la rencontre est très... physique. C'est l'anniversaire de mon plus jeune, Ludo, aujourd'hui (le 18 mars) et il a 11 ans. Cela signifie que vendredi soir et samedi, juste avant la rencontre, nous étions chez moi dans la gestion de l'anniversaire (amis, sortie au Musée des illusions de Montréal, visite de la famille, etc.), mais il se trouve que c'est aussi les CS Games de Montréal, et que j'ai accepté d'aider les organisateurs (et particulier le très chic Martin Dufour) en préparant une compétition. Conséquemment, samedi le 16 mars au matin, je tournais des crêpes pour les enfants en supervisant, à distance, ma compétition, et dimanche le 17 vers 18 h, j'étais à Montréal pour présenter les résultats de ma partie de la compétition et féliciter les équipes qui s'en sont le mieux tirées (bravo!). Un bel événement, bien organisé, avec beaucoup de gens que je connais (en particulier des étudiant(e)s actuel(le)s et plusieurs étudiant(e)s du passé récent)

Ce fiut un bel événement. J'y ai croisé plusieurs anciennes étudiantes et plusieurs anciens étudiants. C'est toujours drôle de se faire reconnaître dans de telles circonstances. Il y avait quand même plus de 200 participant(e)s! Une fois les prix remis, je suis revenù à la course chez moi. Arrivé à la maison vers 19 h 52, juste le temps de me faire un café et de m'installer.

Comme c'est souvent le cas dans des rencontres partiellement virtuelles d'envergure, il y a des irritants techniques au démarrage (le son, surtout). J'ai beaucoup de respect pour les gens comme Jens Maurer qui font en sorte que l'on parvienne à réunir entre 150 et 225 expertes et experts par rencontre dont plusieurs dizaines à distance tout en réussissant à nous permettre de partager des idées complexes sur des sujets sensibles.

Une fois les irritants techniques réglés, John Spicer (Chair de WG21) prend les commandes et explique le déroulement prévu pour la semaine qui débute, insistant sur le code de conduite et les règles à respecter durant une telle rencontre, en particulier celles ayant trait aux votes. Au début de la rencontre, nous sommes 50 participant(e)s à distance. Notez que lors d'une rencontre d'experts ISO, il n'est pas permis d'identifier les gens qui se prononcent ou comment les gens votent, alors vous voyez cette page sous forme censurée.

Les nouveaux membres se présentent. Il y en a quand même beaucoup (pas loin d'une quarantaine), ce qui est souvent le cas quand on se déplace hors de l'Amérique du Nord (ça simplifie les déplacements de gens qui habitent ailleurs). Semble qu'on s'approche des présences de Prague, le plus gros meeting jusqu'ici, juste avant la pandémie. Herb Sutter montre que nous connaissons maintenant les dates de toutes les rencontres jusqu'à 2026. La répartition des groupes de travail par salle est présentée, et chose rare : pas de rencontres de soirée cette semaine.

Je me déplace (virtuellement) ensuite vers CWG où les gros sujets prévus ce « matin » sont P0876, Fiber Contexts (une proposition qui en est à sa version R15, rien de moins!) et P3106 qui vise à clarifier ce qui se passe dans le processus de Brace Elision. On règle la mécanique, on se présente et on commence.

P0876 – Fiber Context

Jens Maurer présente le concept en termes généraux, puis passe la parole à Nat Godspeed qui est co-auteur.

Nat Godspeed explique d'abord que la définition qui distingue un fiber d'un thread a longtemps été contentieuse. On a le texte « A fiber is a single flow of control within a program, including the initial invocation of a specific top-level function, and recursively including every function invocation subsequently executed by the fiber. The execution steps of a fiber are performed by a thread ». On fait remarquer que ceci déplace des mots traditionnellement associés à un thread pour les ramener à un niveau plus granulaire.

La résomption d'un fiber doit se faire dans le thread où il a été suspendu, sinon on a un cas de UB. Il y a un owning thread par fiber et celui-ci ne peut pas changer. Chaque thread est le owning thread de son fiber par défaut. Le fiber par défaut d'un thread est un implicit fiber; les explicit fibers sont créés par le code client. On essaie de voir si on est obligés de garder le UB ou si ça peut être évité. Jens Maurer propose d'y aller par voie terminologique. Nat Godspeed signale qu'un programme voudra parfois créer plusieurs fibers tôt dans le programme pour amortir les coûts et craint un choix de mot qui empêcherait d'y arriver. Hubert Tong distingue ce que permet le Core Wording de ces détails.

Nat Godspeed rappelle que le design discuté existe (dans Boost) depuis 2009. Il ne souhaite pas le changer. Hubert Tong dit souhaiter seulement changer les mots utilisés pour le décrire; instancier un fiber est en fait allouer de la mémoire, du moins au sens de l'implémentation, mais le sens dans le Core Wording est bien différent. Mike Miller dit que dans le Core Wording, tant qu'il n'y a pas eu une évaluation dans la machine abstraite du langage, rien ne se produit. On fouille et le UB semble venir du texte de LWG.

On examine les constructeurs proposés pour fiber_context. Un enjeu est que ça dit que le fiber est placé dans un état suspendu, or il n'y a pas encore de fiber à ce stade. On retravaille le texte pour retirer ce paradoxe (du moins dans le contexte du Core Wording).

On note qu'un fiber qui n'est pas en cours d'exécution dans un thread n'a pas de garantie de progression.

Les exceptions sont subtiles avec un fiber et on discute pour un bout. On note qu'il y a des resume() et resume_with() mais pas de suspend() (certains exemples supposent un suspend() mais ce sont des exemples de pseudocode). Hubert Tong fait remarquer que quand on est dans un bloc catch, on détruit une part de contexte en sortant et il y a un risque de changer de fiber pendant le catch bloc, menant à des dangling resources. Un impact vilain est qu'un re-throw pourrait lever une exception différente de celle qui a été attrapée... JENS MAURER signale que EWG devrait être très explicite sur ce qui est acceptable selon eux, avec exemples pour soutenir le tout. On parle de Implementation-Defined Behavior, pas de UB, mais tout de même...

On fait une pause pour « dîner ». Je vais me faire une bouchée (il est 23 h ici et je n'ai pas encore eu le temps de manger). Petite salade, un peu de vaisselle, et un peu de lecture en préparation de l'après-midi. On reverra P0876 plus tard cette semaine (c'est une grosse proposition et on n'a pu qu'en traiter quelques éléments).

D2809R3 – Trivial infinite loops are not Undefined Behavior

On regarde les formes de boucles infinies qui doivent être couvertes. On ne vise que les boucles infinies triviales dans ce cas-ci; on ne veut pas permettre d'autres formes car cela nous empêcherait d'offrir des garanties de forward progress qui sont extrêmement pertinentes.

Une question discutée est de savoir si ceci est trivialement infini :

int x = 42;
while(std::is_constant_evaluated() || --x)
   ;

JF Bastien signale que c'est une boucle qui se termine à l'exécution, mais Jens Maurer signale que c'est trivialement infini à la compilation. Des exemples semblables pourraient surgir par d'autres chemins, par exemple des calculs imprudents sur des nombres à virgule flottante. JF Bastien signale que EWG n'a pas discuté ce cas bien précis (les discussions ont surtout porté sur le contenu de la boucle, pas sur l'expression de contrôle). Il se dit toutefois d'avis que ces cas dégénérés ne seront pas rencontrés en pratique, hors du contexte de tests qui valident les contours de règles du Core Language. Shafik Yaghmour fait remarquer que cet exemple est invalide dans un contexte constexpr, ce qui est amusant (les boucles infinies n'y sont pas permises).

On trouve une manière de tourner la langue de manière à rejoindre l'intention de EWG. On pourra peut-être en arriver à une entente tacite.

On discute ensuite une note qui explique le caractère optionnel de this_thread::yield_forever() sur une implémentation Freestanding. Le texte est nécessaire mais proposé dans un contexte qui le rend difficile à comprendre. C'est pas toujours facile de bouger une note, et les débats sont véhéments.

On reverra (probablement brièvement) cette proposition plus tard cette semaine.

P3075R0 – Adding an Undefined Behavior and IFNDR Annex

Shafik Yaghmour a travaillé fort à préparer une telle annexe. Ce sera un travail éditorial entre lui et Jens Maurer. On fait quelques retouches (ajout de références croisées, de termes qui décrivent le UB dans le texte du standard, etc.). On clarifie qu'il s'agit de Core UB seulement et on détermine une manière convenable de le dire.

On reverra cette proposition plus tard cette semaine, quand elle sera prête à accepter des propositions. Pour la section IFNDR, on blague sur le nombre d'exemples qu'il faudra pour décrire ce qui découle de la ODR. Certains pensent qu'on devrait couvrir Library en plus de Core, mais c'est vraiment différent alors il y a de la résistance...

P3106R0 – Clarifying rules for brace elision in aggregate initialization

James Touton explique que cela vise à résoudre Core Issue 2149 qui relève des incohérences dans la définition de certains agrégats, en particulier les tableaux dont la taille est inconnue et déterminée à partir du nombre d'éléments, ce qui semble ambigu dans le cas des agrégats avec le texte existant. C'est une approche intéressante, qui réinvestit le terme « appertains » pour décrire l'appartenance des éléments au tableau et expliquer comment la déduction devrait se faire.

(brève pause vers « 15 h »; je me prends des grignotines).

On continue à éplucher la proposition, en particulier le choix des termes pour s'assurer que les mots réappropriés d'autres contextes demeurent conséquents. Par exemple, « convertible to » doit-il provoquer une conversion ou constater sa possibilité? Le terme « aggregate member » pose problème (on utilise « elements of an aggregate » en pratique; il y a des nuances dans le cas d'un agrégat vide). On a aussi une phrase qui parle du premier membre de l'agrégat, mais... s'il est vide, comme struct X{}; par exemple?

On a ce chic exemple qui permet d'expliquer la stratégie (ça accompagne l'algorithme de décision) :

struct S1 { int a, b; };
struct S2 { S1 s, t; };
// x et y ont la même valeur
S2 x[2] = { 1, 2, 3, 4, 5, 6, 7, 8 };
S2 y[2] = {
  {
    { 1, 2 },
    { 3, 4 }
  },
  {
    { 5, 6 },
    { 7, 8 }
  }
};

Il y a quelques exemples subtils dans cette proposition. J'ai bien aimé la travailler.

Core Issue 2814 – Alignment requirement of incomplete class type

L'exemple est :

struct X;       // declared, but not defined
int i;
X* p = static_cast<X*>(static_cast<void*>(&i));

Est-ce que la valeur de p est « unspecified »? Hubert Tong a des réserves, et dit que C a la même situation et s'en sort si le contexte le permet. Quand sait-on l'alignement d'un type? On travaille un bout de temps pour statuer que le requis à l'effet que le type doit être complet est raisonnable.

(je dois arrêter à 3 h 18 car mon corps n'en peut plus; il reste encore une heure alors ça pourrait être pire)

Jour 1 – 19 mars 2024

J'arrive avec deux minutes de retard (souper d'anniversaire officiel de Ludo) et les travaux sont débutés.

P0609 – Attributes for Structured Bindings

Aaron Ballman propose de permettre d'annoter les Structured Bindings, en particulier les éléments d'un Structured Binding sur une base individuelle. C'est un petit ajustement grammatical, et Corentin Jabot porte le dossier en personne. On pense en particulier à [[maybe_unused]] ici. Il y a des cas dans le texte du standard où des productions grammaticales semblables pourraient être impactées par ce changement alors on s'interroge sur certaines d'entre elles (en particulier celles en lien avec les boucles for sur des intervalles) : veut-on une homogénéisation de la grammaire? Corentin Jabot va aller consulter EWG pour s'assurer de bien comprendre l'intention.

On examine la question d'une Feature Test Macro pour ce nouveau mécanisme. On en a une pour les Structured Bindings eux-mêmes et une autre pour les annotations, alors on essaie de bien intégrer le tout. Il se peut qu'on accroisse simplement la macro pour Structured Bindings dans ce cas.

Brian Bi fait remarquer qu'on a présentement une note à l'effet qu'on encourage les implémentations à ne pas émettre d'avertissement dans le cas où un élément d'un Structured Binding ne serait pas utilisé. Avec cet ajustement, il pourrait être pertinent de changer cette recommandation. Après discussion, on convient de laisser cette décision aux soins des vendeurs.

On reviendra sur ce sujet plus tard cette semaine.

P2893 – Variadic Friends

Arthur O'Dwyer présente celle-ci. L'idée est de permettre à une classe d'avoir un nombre variadique d'amis. Deux options terminologiques sont proposées; la préférée des autres fait de friend Ts...; pour types T1 et T2 un équivalent à friend T1, T2; ce qui serait une nouveauté grammaticale. Il y a de la résistance dans la salle (ça semble évolutionnaire) mais EWG aurait vu cette approche et aurait eu une crainte à l'effet que ce serait difficile, mais Jody Hagins pense que c'est plus respectueux de l'intention originale et plus sain dans l'ensemble. Arthur O'Dwyer fait remarquer que using T1::f, T2::g; est déjà légal.

Une conséquence de cette proposition est l'injection d'une nouvelle production grammaticale sous name-declaration, nommée friend-type-delcaration, avec tout ce qui en découle.

Il y a des subtilités : on peut (accidentellement ou pas) déclarer des alias en tant qu'amis qui ne sont pas des classes avec ce mécanisme, ce que la proposition demande d'ignorer. Par exemple :

struct X {
   class nested;
};
struct Y {
   using nested = int;
};
template <class ... T> struct Z {
   friend Ts...;
};
int main() {
   Z<X, Y> z; // amis : X::nested (Ok) et int (ignoré)
}

Un autre cas proposé est :

class C;
typedef C Ct;
struct E;

class X1 {
  friend C;                     // OK, class C is a friend
};

class X2 {
  friend Ct;                    // OK, class C is a friend
  friend D;                     // error: D not found
  friend class D;               // OK, elaborated-type-specifier declares new class
};

template <class... Ts> class R {
  friend Ts...;
};

template <class... Ts, class... Us>
class R<R<Ts...>, R<Us...>> {
  friend Ts::Nested...;
  friend Us...;
};

R<C> rc;                        // class C is a friend of R<C>
R<C, E> rce;                    // classes C and E are friends of R<C, E>
R<int> Ri;                      // OK, "friend int;" is ignored

struct E { struct Nested; };

R<R<E>, R<C, int>> rr;    // E::Nested and C are friends of R<R<E>, R<C, int>>

Une bonne partie des échanges porte sur la généralité. Jody Hagins signale que l'intention est d'augmenter les règles existantes tout en les couvrant, pas d'ajouter un cas particulier. Hubert Tong remarque que la grammaire choisie rejette les annotations et les énumérations en tant qu'amis, ce qui n'est pas déraisonnable.

John Spicer fait remarquer que permettre friend class A, class B; lui semble moins pertinent que friend A, B; mais Hubert Tong semble d'avis que les mots qui permettent d'interdire ce cas compliquent beaucoup la proposition.

(brève pause à 10 h 03 heure de Tokyo)

Après la pause, Arthur O'Dwyer signale avoir mis P2893 à jour. On fait le tour et, supposant l'assentiment de EWG pour quelques détails, ça semble prêt.

P2795 – Erroneous behavior for uninitialized reads

Thomas Köppe revient avec une R5 de ce texte qui a fait quelques rondes déjà. La définition actuelle est « Well-defined behavior that the implementation is encouraged to diagnose ». Le erroneous behavior n'est pas permis dans un contexte constexpr. Une implémentation peut terminer l'exécution d'un programme une fois l'exécution d'une opération erronée. Mike Miller demande si on peut arrêter l'exécution avant l'opération erronée plutôt qu'après. Thomas Köppe dit que le moment choisi est « à un moment non-spécifié après exécution de l'opération » ce que Brian Bi qualifie de roulette russe...

Patrice Roy demande des explications quant à l'intention; Thomas Köppe dit que le comportement doit être bien défini même si l'opération est faite. Dit autrement : l'usager peut faire des bêtises si tel est son souhait, mais le comportement ne sera pas une surprise. Autrement dit, écrire int n; int m = n; est erroné, mais donnera un résultat bien défini et la lecture aura lieu. La plateforme peut terminer le programme mais n'est pas obligée de le faire.

 On pourrait désormais utiliser [[indeterminate]] int n; pour conserver le comportement traditionnel (les bytes de l'objet ont une valeur indéterminée) et int n; pour avoir une initialisation implicite avec une erroneous value. Il est subtil de déterminer le moment où une valeur erronée se manifeste dans un programme : est-ce au moment de la lecture d'une variable non-initialisée ou au moment de sa définition, par exemple? Et que se produit-il si un fait un memcpy() qui copie d'un int non-initialisé : est-ce que la destination est erronée? Car memcpy() est... spécial. On aurait un cas où lire un même objet avec = ou avec memcpy() donne des résultats distincts. Shafik Yaghmour réclame un exemple qui clarifie l'intention : lire deux fois la valeur d'un même objet doit-il donner deux fois la même valeur? Par exemple :

int g(int x) {
  return std::sqrt(x); // incorrect si x < 0
}
void f() {
  int a; 
  int b;
  int c = a; // c has an erroneous value : le programme peut être terminé, mais la valeur lue est non-erronée
  assert(a == c); // ok, nécessaire car...
  if(a > 0) // ... nouvelle évaluation de a
     g(a);     // ... il faut que la même valeur soit constatée
  int d = c;
  assert(a == d); // ok : c et d ne sont pas erronés
  std::memcpy(&a, &b, sizeof b);
  assert(a == b);
}

Hubert Tong signale un point important : tant que le compilateur peut suivre à la trace « l'état du programme », on peut garantir des valeurs erronées suppléées (de manière statique, peut-être) lors de lecture erronées, mais si l'adresse d'une variable s'échappe d'une fonction, les lectures de cette variable par la suite devront se faire à l'exécution ce qui fait que deux lectures du même objet pourront donner des valeurs distinctes. Ouille, c'est violent comme constat! Mais c'est le cas de l'appel à std::memcpy() dans l'exemple ci-dessus.

John Spicer demande si les valeurs erronées sont les mêmes peu importe la variable; Thomas Köppe précise que ça peut être une valeur par variable. Hubert Tong demande ce qu'on fait lors de la promotion entière d'une valeur erronée; on a un algorithme de décision et certains ne sont pas convaincus qu'on ait bien traité cette subtilité, mais nous sommes tous d'accord pour dire que promouvoir une valeur erronée devrait résulter en une autre valeur non-erronée (ça reste une opération erronée).

(pause pour le dîner)

On poursuit sur P2795. Brian Bi propose une formulation différente pour alléger le texte mais il y a des dissensions (c'est un sujet extrêmement délicat). On demande de préciser que réaliser une promotion arithmétique sur un entier erroné est une opération erronée mais la valeur résultante est bien définie. Brian Bi fait remarquer que les conversions doivent aussi être examinées, par exemple initialiser un short avec un char erroné (ce ne sont pas les mêmes règles). Toutes ces acrobaties visent à permettre à memcpy() de fonctionner.

Truc subtil : faut exprimer l'idée que lire une valeur erronée donne une valeur non-erronée équivalente à la valeur erronée originale... Évidemment, si on lit une valeur indéterminée plutôt qu'une valeur erronée, le code demeure UB comme il l'est depuis toujours.

On passe beaucoup de temps à raffiner et à polir (comme CWG seul sait le faire) les exemples de la proposition.

Patrice Roy soulève que l'annotation [[indeterminate]] lui semble ne pas faire de sens sur un paramètre de fonction. On débat de la question. On a un étrange exemple, mais après ajustement il fait du sens :

struct T { T(){} int x; };
void h(T t [[indeterminate]] = T()) {
  f(t.x);   // undefined behavior if default argument is used
}

C'est un truc qui sera rare et obscur en pratique, manifestement. Après plusieurs heures de travail, on en arrive à quelque chose qu'on pourra soumettre au votre pour la plénière de samedi. Ça doit passer par LWG au préalable, cela dit.

P2748R4 – Disallow Binding a Returned Glvalue to a Temporary

Cette proposition vise à corriger un bogue introduit involontairement et qui brise le code de certaines fonctions de la bibliothèque standard. Elle était à peu près ficelée depuis Kona. On la soumettra au vote samedi.

P2747R1 – constexpr placement new

Barry Revzin a fait quelques ajustements à cette proposition (que je souhaite ardemment). Hubert Tong fait remarquer qu'une autre proposition est en vol et recoupe avantageusement certains cas de celle-ci. La proposition ne permet pas n'importe quel appel à Placement New dans un contexte constexpr mais balise les cas qui semblent raisonnables à ce stade. Ce sera soumis au vote samedi.

P1061R7 –Structured Bindings can introduce a Pack

Une proposition fort intéressante que celle-ci. Barry Revzin explique que la nouveauté est l'introduction formelle d'un « contexte semblable à celui d'un template » pour donner forme à ce que ce mécanisme provoquait. Ça va permettre des trucs comme :

struct C { int j; long l; };

int g() {
    auto [ ... i ] = C{ 1, 2L };
    return ( [c = i] () {
        struct C {
            int f() requires (sizeof(c) == sizeof(int)) { return 1; }
            int f() requires (sizeof(c) != sizeof(int)) { return 2; }
        };
        return C{}.f();
    } () + ...  + 0 );
}

int v = g(); // OK: v == 3

Patrice Roy fait remarquer que ceci suppose sizeof(int)!=sizeof(long); on ajustera pour clarifier. Notez la présence de requires et de Fold Expression dans une fonction a priori non générique!

Il y a beaucoup de résistance à l'approche tentée ici de la part de CWG : on tente d'introduire les idées dans la section sur la portée, mais CWG suggère de les placer dans la section sur les templates. John Spicer se dit d'avis que c'est un très gros changement au langage pour obtenir un gain qui semble orthogonal aux conséquences qui en sont induites. Jason Merrill pense que le changement concorde avec l'approche car il pense qu'on peut se retrouver avec des noms dépendants au passage.

Jens Maurer demande de faire en sorte que le texte clarifie le début et la fin de la section « templateish » implicite, et demande si une telle section est introduite aussi si nous sommes déjà dans un template. Hubert Tong demande si ces portées implicites peuvent être exportées d'un module. Brian Bi fait remarquer que cette nouveauté peut « avoir plus d'accolades fermantes que d'accolades ouvrantes » dans sa forme actuelle.

Suivant l'approche proposée, Jens Maurer demande où se trouve le point d'instantiation du « template region » implicite. On regarde le texte préexistant. Barry Revzin le positionne à la fin de la région implicite.

John Spicer montre que la proposition est un peu... empoisonnée. Si on a initialement :

// code existant
template <class T> void g(T);

int f() {
   struct C {};
   g(c{}); // initialement, n'est pas un appel dépendant
}

... puis on ajoute un pack introduit par un Structured Binding selon cette approche, on change le sens du code :

auto [ ...i ] = { 1,2 }; // on introduit ceci
// code existant
template <class T> void g(T);

int f() {
   struct C {};
   g(c{}); // devient subitement un appel dépendant
}

On construit d'autres exemples où le passage d'appels non-dépendants à dépendants change les fonctions qui sont appelées. Brrr... Jason Merrill propose une définition différente et plus implémentable du point d'instanciation pour la région implicite. Hubert Tong montre qu'on peut aussi vivre des changements au processus d'ADL qui trouve les déclarations amies... John Spicer montre que ça impacte la sémantique de constexpr.. Ouf! Barry Revzin dit qu'on vient de découvrir comment implémenter le static_if du langage D en C++!

Autres cas :

auto [ ... i ] = { 1, 2 };

// existing code

void g(...);  // #1

void f()
{
  struct C { using M = int; };
  g(C());  // turned into dependent call; used to call #1, now calls #2

  /* typename */ C::M *x;   // "typename" now needed
}

template<class T>
void g(T);     // #2

Encore un autre cas :

struct X {

  struct D {};

  void f()
  {
    struct C {  };
    h(D(), C());    // not a problem
  }

  friend void h(D, ...);  // hidden friend, only found via ADL
}; // <---

void i() {
  if constexpr (sizeof(char) > 1) {
    g(1,2,3);  // ill-formed without structured binding pack; IFNDR with it
  }
}

Après tout ce tourbillon de surprises, Barry Revzin suggère d'interdire les introductions de packs variadiques dans un namespace. C'est clairement tout un panier de crabes! On échange quelques suggestions alternatives de design. Barry Revzin note (avec justesse) qu'avec la réflexivité à l'horizon, nous allons de plus en plus avoir besoin de mécanismes comme celui proposé ici alors il va clairement falloir trouver une solution.

On regarde ensuite les changements proposés à la grammaire. C'est une proposition très ambitieuse et très créative, mais elle a une multitude d'effets subtils. Par exemple, ceci serait permis (ou du moins son équivalent avec [...v] local à une fonction) :

struct C {
  int i;
  long l;
};
auto [...v] = C{ 1, 0 };
C k(int, long);              // #4
C k(decltype(v)... p) {      // defines #4
  return C{p...};            // non-dependent function parameter pack p
                             // is instantiated immediately ([temp.variadic])
}

Éventuellement, on arrête pour reprendre demain. Il est 4 h 30 du matin ici alors dodo pour moi...

Jour 2 – 20 mars 2024

(j'arrive en retard car je donnais un cours du soir, que j'ai écourté; à mon arrivée on travaille sur Core Issue 1954)

Core Issue 2476. placeholder-type-specifiers and function declarators

Les mots du standard disqualifient un truc comme ceci...

int f();
auto (*fp)()=f;

...ou comme ceci...

int f();
auto (*fp)() -> auto = f;

... qui semblent raisonnables. Davis Herring a une formulation qui semble fonctionner (merci!). Ça sera soumis au vote samedi.

2533. Storage duration of implicitly created objects

On a des objets dont la durée de vie débute implicitement, par exemple dû à malloc(), mais la durée vie de l'espace dans lequel ces objets habite est plus obscure. On couple la durée de vie des temporaires inventées par ces créations implicites à celle des objets alloués dynamiquement pour que les mêmes règles s'appliquent. Notez qu'on parle d'objets primitifs ici; on ne veut pas créer des objets dont les destructeurs ont des effets de bord.

2546. Defaulted secondary comparison operators defined as deleted

On nous rapporte que l'exemple suivant n'est pas pleinement supporté par le texte du standard :

struct HasNoLessThan { };
struct C {
    friend HasNoLessThan operator<=>(const C&, const C&);
    bool operator<(const C&) const = default;  // OK, function is deleted
};

La proposition de résolution semble raisonnable.

2547. Defaulted comparison operator function for non-classes

Les règles semblent permettre de faire :

enum E { };
bool operator==(E, E) = default;  // well-formed?

La question est de savoir si c'est ce qu'on veut. Il y a des ajustements périphériques proposés ici alors on regarde l'ensemble de l'oeuvre. La proposition demande que le type soit complet et impose quelques restrictions sur la fonction de comparaison, permettant :

struct S;
bool operator==(S, S) = default;  // error: S is not complete
struct S {
  friend bool operator==(S, const S&) = default; // error: parameters of different types
};
enum E { };
bool operator==(E, E) = default;  // error: not a member or friend of a class

Ça semble tenir la route.

2560. Parameter type determination in a requirement-parameter-list

Quel est le type de p dans :

template<typename T>
  requires requires (T p[10]) { (decltype(p))nullptr; }
int v = 42;
auto r = v<int>; // well-formed? 

On s'entend pour dire que c'est le decayed type, avec l'accord de EWG. On envoie ça au vote. Il y a eu des blagues croustillantes en étudiant celle-là.

2568. Access checking during synthesis of defaulted comparison operator

La question est de savoir si ceci est légal :

struct Base {
protected:
  bool operator==(const Base& other) const = default;
};

struct Child : Base {
  int i;
  bool operator==(const Child& other) const = default;
};

La réponse semble être que oui, mais que les qualifications d'accès sont vérifiées comme à l'habitude.

2588. friend declarations and module linkage

La question est de savoir le linkage de f() dans :

export module Foo;
class X {
  friend void f(X); // #1 linkage?
};

C'était un peu une patate chaude alors on a consulté EWG. C'est encore un peu particulier : si la classe est exportée, f(X) l'est aussi mais seulement si c'est une définition. Davis Herring pense que la résolution proposée n'est pas pleinement conforme aux souhaits de EWG. On le retravaille un peu. L'exemple qui préoccupait EWG était :

export module Foo;
export class Y;
// maybe many lines later, or even a different partition of Foo
class Y {
  friend void f(Y); // #2 linkage?
};

(brève pause, puis on poursuit)

Après un travail non négligeable, on pense avoir réussi à exprimer ce qu'on voulait exprimer. Le texte qui nous y mène est plutôt différent de celui que nous avions au préalable. On va se donner un peu de temps pour y réfléchir avant de le passer au vote, étant donné la subtilité des ajustements apportés.

453. References may only bind to “valid” objects

Il y a une proposition de résolution sur la table, incluant quelques exemples :

int& f(int&);
  int& g();

  extern int& ir3;
  int* ip = 0;

  int& ir1 = *ip;     // undefined behavior: null pointer
  int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
  int& ir3 = g();
  int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer

Hubert Tong suggère d'ajouter l'exemple suivant :

char x alignas(int);
int &ir5 = *reinterpret_cast<int *>(&x);  // undefined behavior: int glvalue to char object

Davis Herring note en riant que sans l'ajustement associé à cet exemple nouvellement ajouté, nous aurions accidentellement brisé la quasi-totalité des programmes C++ sur terre. Ouf!

2634. Avoid circularity in specification of scope for friend class declarations

On a un exemple étrange :

auto f(struct X* ptr) {
  struct D {
    private:
      int d;
      friend class X;      // #1
  };
  return D{};
}
X* b = 0;
struct X {
  void show() {
    auto t = f(0);
    t.d = 10;              // #2 error: ::X is not a friend of f::D
  }
};

Les mots existants provoquent une confusion qui mène à des divergences d'implémentation. La résolution proposée est une clarification simple qui résout l'imbroglio.

2637. Injected-class-name as a simple-template-id

La grammaire permet en théorie de faire des trucs avec les constructeurs qui ne sont pas souhaitables... ou souhaités. La proposition est très simple (remplacer « class-name » par « component name of the class-name »). On passera au vote samedi.

2638. Improve the example for initializing by initializer list

Shafik Yaghmour signale que les exemples dans une section sont insatisfaisants. La suggestion est de passer de ceci :

struct S {
  S(std::initializer_list<double>); // #1
  S(std::initializer_list<int>);    // #2
  S(std::initializer_list<S>);      // #3

  // ...
};
S s1 = { 1.0, 2.0, 3.0 };  // invoke #1
S s2 = { 1, 2, 3 };        // invoke #2
S s3{s2};                  // invoke #3 and not the copy constructor

... par cela :

struct S {
  S(std::initializer_list<double>); // #1
  S(std::initializer_list<int>);    // #2
  S(std::initializer_list<S>);      // #3
  S();                              // #4

  // ...
};
S s1 = { 1.0, 2.0, 3.0 };  // invoke #1
S s2 = { 1, 2, 3 };        // invoke #2
S s3{s2};                  // invoke #3 and not the copy constructor
S s4 = { };                // invoke #4

On change une paire d'accolades par une paire de parenthèses et ça va.

2657. Cv-qualification adjustment when binding reference to temporary

Le texte actuel ne dit pas clairement à quel objet temporaire est associé une référence dans certains contextes. Les changements sont terminologiques. On ajoute un exemple :

constexpr int f() {
   const int &x = 42;
   const_cast<int&>(x) = 1; // UB
   return x;
}

constexpr int z = f(); // error : not a constant expression

C'est plus ou moins nécessaire mais ça ne nuira pas non plus. On passera ça au vote samedi.

2661. Missing disambiguation rule for pure-specifier vs. brace-or-equal-initializer

On a un problème d'ambiguïté dans la grammaire :

member-declarator:
   declarator virt-specifier-seq[opt] pure-specifier[opt]
   declarator brace-or-equal-initializer[opt]

pure-specifier:
   = 0

Le =0 peut correspondre à deux trucs (méthodes virtuelles pure, initialisation de données membre) et que ça peut mener à des ambiguïtés. L'ajustement est de faire « sauter » un opt de là et d'ajouter une règle de désambiguation en prose pour que le cas virtual ... = 0 soit considéré différemment des autres. On y va pour un vote samedi.

2668. co_await in a lambda-expression

Le texte actuel ne permet pas d'utiliser co_await dans une expression lambda, ce qui semble accidentel. L'idée est qu'on parle de co_await dans le corps d'une fonction mais que le corps des lambdas n'est pas un corps de fonction sur le plan grammatical. Le correctif est très simple. On votera samedi.

2689. Are cv-qualified std::nullptr_t fundamental types?

La question posée est à savoir si const nullptr_t est un type fondamental (pas clair dans le texte). On clarifie que nullptr_t et const nullptr_t sont des types distincts.

2700. #error disallows existing implementation practice

On fait remarquer que les obligations induites par #error et #warning ne sont pas conformes aux pratiques courantes. La proposition sur la table est pas mal, mais ... Davis Herring estime que le texte brise tous les programmes. Jens Maurer fait remarquer que ce sur quoi se base Davis Herring n'existe plus, ayant été refactorisé par le passé. Ok, on votera là-dessus samedi.

2707. Deduction guides cannot have a trailing requires-clause

On constate que ce serait bien, mais que la grammaire ne le permet pas. Le changement proposé à la grammaire corrige cette situation. On votera là-dessus samedi.

2728. Evaluation of conversions in a delete-expression

La phrase « The cast-expression in a delete-expression shall be evaluated exactly once » ne dit pas clairement si la possible conversion en pointeur fait partie de ce « exactly once ». Une refactorisation est requise. Davis Herring recommande de se débarasser du paragraphe en question tout simplement. On fouille, on gratte et... plusieurs paragraphes (anciens!) sont brisés ici alors on procède à une réparation détaillée. Ils sont apparemment brisés depuis 2012! Jens Maurer fait remarquer que la section sur new et delete, donc [expr.delete] et le voisinage, est due pour une réécriture.

On passe près de 45 minutes là-dessus. Ça demeure Tentatively Ready pour le moment.

2745. Dependent odr-use in generic lambdas

On demande l'ajout d'un exemple clarifiant l'intention. Ce qui est proposé est :

void g() {
  constexpr int x = 1;
  auto lambda = [] <typename T, T V = x> {};  // OK
  lambda.operator()<int, 1>();         // OK, does not consider x at all
  lambda.operator()<int>();            // OK, does not odr-use x
  lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
}

void h() {
  constexpr int x = 1;
  auto lambda = [] <typename T> { (T)x; };  // OK
  lambda.operator()<int>();            // OK, does not odr-use x
  lambda.operator()<void>();           // OK, does not odr-use x
  lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
}

Hubert Tong pense que le premier des deux exemples proposés est problématique, étant illégal pour d'autres raisons : on ne sait pas de quel x on parle, ça dépend de T (le deuxième semble correct). On cherche des manières de mettre en relief ce qu'on vise à expliciter. On en arrive à :

void g() {
  constexpr int x = 1;
  auto lambda = [] <typename T, T V = ((T)x, 0)> {};  // OK
  lambda.operator()<int, 1>();         // OK, does not consider x at all
  lambda.operator()<int>();            // OK, does not odr-use x
  lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
}

void h() {
  constexpr int x = 1;
  auto lambda = [] <typename T> { (T)x; };  // OK
  lambda.operator()<int>();            // OK, does not odr-use x
  lambda.operator()<void>();           // OK, does not odr-use x
  lambda.operator()<const int&>();     // error: odr-uses x from a context where x is not odr-usable
}

On votera sur ça samedi.

(pause pour le dîner)

Au retour, Jens Maurer organise la caméra pour qu'on puisse voir les visages des amis là-bas (merci Jens!) et on fait du Core Issues Processing car il nous manque plusieurs joueurs (ça débat des contrats dans une autre salle, un sujet extrêmement chaud et qui génère des débats depuis plusieurs années... Si ça fonctionne, ce sera un ajout majeur au langage!)

2746. Checking of default template arguments

La question posée est de savoir si ceci est bien formé même si x n'est pas une constant expression du fait que f<5>() ne se sert pas de x :

static int x = 1;
template <auto y = x> void f() {}

Le texte décrivant cette situation pourrait être plus clair. Il y a deux questions en fait : l'accepte-t-on, et comment décrit-on la mécanique qui sous-tend notre choix. On l'accepte (il est possible de l'instancier correctement) mais on rejette en tant que ill-formed dans le cas par défaut. On soumettra ça au vote samedi.

2748. Accessing static data members via null pointer

Si on a ceci :

struct C { static int foo; };
C* c = nullptr;

... alors l'écriture c->foo est incorrecte dû à la réécriture de c->foo en (*c).foo ce qui est UB, mais cette réécriture est décrite trop tard dans le processus algorithmique. On corrige le tir pour que ce soit légal comme de l'est déjà avec une fonction static et ça sera soumis au vote.

2771. Transformation for unqualified-ids in address operator

Selon le texte actuel, ceci peut surprendre :

struct A{
  int a;
  void show(){
    int* r = &a; // #1 
  }
};

... dû à une règle de transformation qui remplace &a par &(this->a) au mauvais endroit. Faut ajuster le texte un peu pour que la théorie rejoigne la réalité.

2775. Unclear argument type for copy of exception object

Le texte qui explique comment les objets modélisant une exception sont construits a besoin d'un peu d'amour, mettant ensemble deux aspects distincts (ceux qui touchent le std::exception_ptr qui porte l'exception et ceux qui décrivent l'objet lui-même). Il y a aussi un enjeu du fait que créer un objet lors d'une levée d'exception peut être un premier ODR-use de son constructeur, ce qui joue dans les points d'instantiation des templates par exemple.

Le nouveau texte me semble meilleur (plus simple, plus clair) que son prédécesseur.

2777. Type of id-expression denoting a template parameter object

Il y a divergence d'implémentation avec un cas comme :

struct A {};

template<auto a, auto x>  // also consider A a and const auto x
int f() {
  decltype(a) b;          // also consider decltype((a))
  A& rb = b;
  decltype(x) y;
  int& ry = y;
}

int x = f<A{}, 42>();

Les paramètres a et x sont des const dans ce cas, alors des références non-const ne devraient pas passer, mais le texte était un peu « mou ». On corrige et ça fait du bien. Beaucoup de débats cela dit. On soumet ça au vote samedi.

2803. Overload resolution for reference binding of similar types

On nous signale que ceci :

int foo(int*& r);       // #1
int foo(const int* const& r); // #2

int *p;
int x = foo(p);

... a la particularité que les implémentations préfèrent #1 à #2 même si nous n'avons pas de règle pour désambiguer la situation. Avec les règles proposées, on aurait :

int f(const volatile int *);
int f(const int *);
int i;
int j = f(&i);  // calls f(const int*) // note : ceci est subtil
int g(const int*);
int g(const volatile int* const&);
int* p;
int k = g(p);          // calls g(const int*)

... de même que :

int h1(int (&)[]);
int h1(int (&)[1]);
int h2(void (&)());
int h2(void (&)() noexcept);
void g2() {
  int a[1];
  h1(a);            // calls h1(int (&)[1])
  extern void f2() noexcept;
  h2(f2);            // calls h2(void (&)() noexcept)
}

On procède. Vote samedi.

2809. An implicit definition does not redeclare a function

On essaie de comprendre l'enjeu, qui semble recouper le concept de Hidden Friend, probablement avec operator<=>() je suppose (il n'y a pas d'exemple tristement). Cela dit, le texte semble correct et l'auteur du Core Issue (qui est présent) semble d'accord.

2810. Requiring the absence of diagnostics for templates

Il y a un passage où on dit dans le standard de ne pas émettre de diagnostics si une spécialisation valide de template peut être générée, mais les vendeurs nous rappellent qu'ils peuvent émettre des diagnostics s'ils le veulent. On n'a qu'à rayer cette note.

(brève pause)

2811. Clarify "use" of main

Le standard dit « The function main shall not be used within a program » mais c'est... inadéquat.

On propose « The function main shall not be named by an expression » ce qui semble plus près des besoins. Cela signifie que même decltype(main) sera interdit, mais cela semble être l'intention de CWG. Ça passera au vote samedi.

2813. Class member access with prvalues

Selon la proposition, ceci devrait être bien formé :

struct X {
  X() = default;

  X(const X&) = delete;
  X& operator=(const X&) = delete;

  void f(this X self) { }
};

void f() {
   X{}.f();   // OK?
}

Ici, le recours à Deducing This fait paraître f() comme acceptant explicitement un paramètre alors qu'il s'agit d'un passage implicite. On adapte le texte pour parler de la matérialisation particulière dans ce cas de la temporaire pour les besoins de l'appel. Ça passe le test, on soumettra au vote.

2818. Use of predefined reserved identifiers

La restriction quant à l'usage de noms réservés comme __abc empêche le code client... de tester les Feature Test Macros, par exemple, ou encore __cplusplus! Hé la la... On ajoute des exceptions à cette restriction (en gros : tous les mots qui sont dans le standard lui-même, ce qui laisse le reste aux implémentations). Patrice Roy soulève le fait que quelque chose comme __stdcall devient interdit dans un programme C++ conforme, mais on convient que c'est pas portable par définition alors c'est comme ça!

Celle-là sera Tentatively Ready pour le moment.

2820. Value-initialization and default constructors

Le texte actuel laisse entendre qu'on initialise certains objets deux fois, c'est amusant, mais on examine une clarification du comportement de ce type d'initialisation. C'est bon, on soumettra au vote.

2822. Side-effect-free pointer zap

Certains passages du standard peuvent être interprétés comme introduisant des Data Races dans la machine abstraite en lien avec la gestion de l'espace sous-jacent aux pointeurs. L'ajustement terminologique est non-trivial. Si vous êtes curieuse ou curieux du genre de texte qu'on étudie et qu'on travaille dans de groupe, on parle ici de :

« A pointer value P is valid in the context of an evaluation E if P is a null pointer value, or if it is a pointer to or past the end of an object O and E happens before the end of the duration of the region of storage for O. If a pointer value P is used in an evaluation E and P is not valid in the context of E, then the behavior is undefined if E is an indirection or an invocation of a deallocation function, and implementation-defined otherwise. [ Footnote: Some implementations might define that copying such a pointer value causes a system-generated runtime fault. -- end footnote ] [ Note: P can be valid in the context of E even if it points to a type unrelated to that of O or if O is not within its lifetime, although further restrictions apply to such pointer values (6.7.3 [basic.life], 7.2.1 [basic.lval], 7.6.6 [expr.add]). —end note]  »

Le texte est pas mal. On ajoute quelques références croisées et c'est pas mal ça. Vote samedi.

2824. Copy-initialization of arrays

On a permis ceci un peu par accident :

std::string arr[] = "some string";

On va obliger les accolades. Bonne chose.

2825. Range-based for statement using a braced-init-list

Ceci :

for (int i : { 1, 2, 3 })  // argument-dependent lookup for begin(std::initializer_list<int>)
    /* ... */;

... fonctionne mais probablement pas par le bon chemin. La proposition recommande de préférer la fonction membre begin() si elle existe. Prête pour vote samedi.

2826. Missing definition of "temporary expression"

C'est bête, mais on utilise ce terme mais il n'est défini nulle part. On ajuste la chose (c'est beaucoup de texte qui s'insère dans un contexte, je vous passe les détails). Prête pour vote samedi.

2828. Ambiguous interpretation of C-style cast

L'interprétation faite de transtypages C est ambiguë. Par exemple, ceci :

int*** ptr = 0;
auto t = (int const*const*const*)ptr;

... peut être vu comme ceci :

const_cast<int const * const * const *>(static_cast<int * * const *>(ptr));

... ou encore comme cela :

const_cast<int const * const * const *>(static_cast<int * const * const *>(ptr));

Brian Bi souligne que le problème est réel mais que l'exemple est incorrect (const_cast se produit d'abord, static_cast ensuite). Il y a plusieurs exemples étranges dans la proposition. Une fois les ajustements faits, ça semble acceptable. Vote samedi.

2830. Top-level cv-qualification should be ignored for list-initialization

C'est mineur mais ça compte : quand on initialise un objet avec { v0, v1, v2, ... } on recommande d'ignorer les cv-qualifications (dont const int peut initialiser un int).

Le changement est très mineur. Vote samedi.

2831. Non-templated function definitions and requires-clauses

Il y a un trou dans le texte quant aux clauses requires sur des définitions de fonctions (le cas des déclarations est plus clair). On travaille un certain temps car le texte proposé utilise des mots bannis comme « may » qui ne sont pas appropriés pour un standard ISO. Après une bonne trentaine de minutes, ça semble prêt (j'aime bien le texte que nous avons obtenu).

2845. Make the closure type of a captureless lambda a structural type

Un changement récent a fait de toutes les fermetures des types qui ne sont pas structurels. Cela cause des problèmes :

template <auto V>
  void foo() {}

void bar() {
  foo<[i = 3] { return i; }>();   // #1: error
  foo<[]{}>();                    // #2: error
  foo<+[]{}>();                   // #3: OK, a function pointer is a structural type
}

On ajoute une clause sur template-equivalent pour couvrir les lambdas sans affecter outre mesure leur nature profonde. Prête pour vote samedi.

2846. Out-of-class definitions of explicit object member functions

On s'attend à ce que le code suivant fonctionne, et nos implémentations l'acceptent :

struct A {
  void f(this A&);
};
void A::f(this A&) { }    // #1

... mais le texte du standard ne le permet pas. La solution proposée convient.

2848. Omitting an empty template argument list for explicit instantiation

On pensait avoir permis d'omettre les <> vides pour une spécialisation dans :

template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

// instantiate sort(Array<int>&) -- template-argument deduced
template void sort<>(Array<int>&);

Le texte proposé semble corriger ce problème. Vendu.

2849. Parameter objects are not temporary objects

Il y a une petite incohérence dans le texte. La proposition convient. Vendu.

2850. Unclear storage duration for function parameter objects

Le texte est relativement clair pour la durée de vie des objets passés en paramètre à une fonction, mais moins pour l'entreposage sous-jacent. Un solide travail s'ensuit pour donner un ordre clair sur la fin de la vie des objets et de leur espace d'entreposage pour ces objets. Ok, ça semble tenir la route.

2851. Allow floating-point conversions in converted constant expressions

On peut passer un float en paramètre à un template<float> si la valeur est connue à la compilation, mais on ne peut pas passer un float à un template<long double>. Il nous manque des provisions pour les conversions qui son non-narrowing. Vendu.

2853. Pointer arithmetic with pointer to hypothetical element

Il manque quelques mots pour permettre de référer à une adresse un byte après le dernier élément d'une séquence (c'est bien traité par les compilateurs, mais faut quand même ajuster le texte). Changement très mineur. Vendu.

2855. Undefined behavior in postfix increment

Oh, pas gentil. Ceci est UB :

int8_t x = 127;
x++;

... mais ceci n'est pas UB :

int8_t x = 127;
++x;

... car défini comme x += 1; qui est défini comme x = (int)x + 1; une fois les conversions habituelles appliquées. Oh boy... On procède avec la proposition.

2856. Initialization with empty braces

On a ceci :

struct A {
  explicit A(int = 10);
   A() = default;   // converting constructor (11.4.8.2 [class.conv.ctor] paragraph 1)
};

A a = {};       // #1, copy-initialization
  
int f(A);
int x = f({});  // #2

A b;            // #3

... où #1 et #2 sont acceptés par certains compilateurs et ambigus par d'autres. La proposition sur la table est trop complexe pour le temps qu'il nous reste.

2857. Argument-dependent lookup with incomplete class types

Truc subtil, mais avec une résolution simple. Vendu.

On arrête pour aujourd'hui... Ouf!

Jour 3 – 21 mars 2024

Les travaux reprennent à l'heure. Nous sommes plus nombreux qu'hier; la journée d'hier fut sportive! On vise une pleine matinée de travail en vue des votes de samedi, mais cet après-midi il nous manquera des joueurs dû à une rencontre concurrente sur les contrats alors on travaillera sur des propositions qui sont moins susceptibles d'être votées samedi.

P3034R0 – Module Declarations Shouldn’t be Macros

Michael Spencer dit que EWG a vu la proposition et souhaite la traiter comme un DR. L'idée est qu'avec la grammaire actuelle, un nom de module peut être une macro, ce qui force le traitement du fichier entier pour comprendre son nom. C'est terriblement inefficace. On note que les noms des annotations passent par les mêmes productions grammaticales mais que pour les annotations, des macros conviennent tout à fait.

Le fait qu'on joue dans la section sur les directives du préprocesseur demande une prudence particulière (ça tend à se propager ailleurs dans le code source). Après une trentaine de minutes, on renvoie l'auteur avec des suggestions de réécriture pour éviter des conséquences indésirables. On remarque qu'il n'est pas clair si on supporte un module avec comme nom un mot clé, par exemple « module virtual; ».

On a une alarme de tremblement de terre. C'est pas discret!

P0609 – Attributes for Structured Bindings

Corentin Jabot revient avec le texte et les réponses aux questions posées à EWG. En gros, on nous laisse procéder. On étudie donc les plus récents changements pour s'assurer que l'intention est bien portée par les mots.

Jens Maurer nous informe que P2748, sur lequel nous avons travaillé lundi, n'était pas la bonne version de la proposition alors nous devons la réexaminer (les ajustements faits depuis nous conviennent, mais il y a des débats; c'est quand même subtil quand on discute de matérialisation de variables temporaires...). On finit par trouver un problème de fond, et on débat car on aimerait la soumettre au vote (les mots sont incorrects mais les implémenteurs vont comprendre ce qu'ils doivent faire).

On revient sur P0609 ajusté. Reste un peu de travail à faire. On le reverra.

D3106R1 – Clarifying rules for brace elision in aggregate initialization

James Touton l'a retravaillé depuis lundi.

(brève pause; je ne sais pas si c'est l'alerte de tremblement de terre plus tôt mais les membres de CWG sont turbulents aujourd'hui)

On valide le texte, les exemples et le narratif des exemples. Après une trentaine de minutes, ça semble convenir et on le soumettra pour vote samedi.

P0609 – Attributes for Structured Bindings

Corentin Jabot a fait les ajustements demandés. Ce sont pour l'essentiel des ajustements éditoriaux. On le soumettra pour votre samedi.

P3034R0 – Module Declarations Shouldn’t be Macros

Michael Spencer revient avec les modifications demandées. On discute quand même longtemps, pour s'assurer que le texte soit correct (il y a des fautes, et on parle du symbole '(' dans le texte ce qui fait une parenthèse non-balancée... Aberration!)

D2809R3 – Trivial infinite loops are not Undefined Behavior

JF Bastien revient avec un texte ajusté. Quand on joue dans le comportement de la machine abstraite, c'est toujours profond... Un truc important est qu'une boucle infinie triviale doit reposer sur l'évaluation manifestement constexpr de la condition.

Davis Herring fait remarquer qu'avec le texte actuel, une horreur comme :

void g();
[[noreturn]] void f() {
   for(;;) {
      try {
          g();
      } catch(...) {
      }
   }
}

... n'est pas trivialement infinie mais, à cause du [[noreturn]], pourrait être considérée comme « progressant » et être vue comme légale... Il reste quelques retouches à faire mais ça semble presque prêt. On le reverra cette semaine, probablement pour la dernière fois.

P2795 – Erroneous behavior for uninitialized reads

LWG a fait quelques retouches (Thomas Köppe est d'avis que ce sont des clarifications pertinentes) alors il nous fait le regarder à nouveau. L'une des retouches impacte la définition de bit_cast et vise à réduire la redondance. On parle de « erroneous bits » ce qui cause un inconfort (on parle habituellement de bytes), mais Hubert Tong fait remarquer que ça permet de parler des bit fields alors ça semble convenir. Hubert Tong fait par contre remarquer qu'on peut avoir à la fois des bits erronés et des bits non-erronés dans un même byte... Ouch. On essaie de voir si le recours aux bit fields est pertinent car ça complique le portrait : on peut avoir des bits erronés et des bits indéterminés dans un même byte.

Patrice Roy se dit d'avis que si on a des bits erronés et des bits indéterminés dans un même byte, le byte est indéterminé par subsomption. Jens Maurer semble du même avis. On travaille pendant un bout et Thomas Köppe finit par constater que pour le moment, on devra accepter que bit_cast permettra d'échapper à notre effort de sécurisation (on laissera des mots pour l'indiquer, le temps qu'on trouve une solution).

Hubert Tong mentionne qu'on pourrait avoir une provision à l'effet que si un objet a des bits erronés et ces bits sont utilisés, alors l'objet entier est considéré erroné. Jens Maurer pense que si au moins un bit d'un objet est erroné, cela teinte l'objet tout entier. Une conséquence serait que si un bit d'un int est erroné et si on bit_cast ce int dans un short[2] alors les deux short sont erronés... même si le int était erroné parce qu'on avait initialement fait un bit_cast dessus à partir de ce short[2] dont un seul des deux short était erroné!

(pause pour le dîner; il est 23 h ici)

Comme prévu, nous sommes un plus petit groupe cet après-midi dû aux contrats qui sont discutés non-loin.

P2786 – Trivial Relocatability For C++26

Mungo Gill présente l'intérêt de cette propriété tellement souhaitée pour l'optimisation de nos conteneurs. La proposition comprend un nouveau mot-clé contextuel (en plus de final, override, module et import) soit trivially_relocatable, de même qu'une nouvelle fonction, trivially_relocate(). L'objectif est d'offrir le support minimal requis pour ce précieux mécanisme, puis de construire le reste sur la base de cette proposition par la suite.

Les types trivialement relocalisables sont une nouvelle catégorie qui inclut les scalaires habituels. Pour les lambdas on ne sait pas ce qu'il advient car les implémentations ont de la latitude.

Chose intéressante : on a trivially_relocatable(constant-expr) pour permettre l'application conditionnelle de cette qualification. Certains s'interrogent sur l'intérêt de cette manoeuvre; on parle de l'application de trivially_relocatable(bool) sur un union anonyme (weird...). On fait remarquer que les parenthèses à cet endroit pourraient nuire à l'introduction syntaxique de métaclasses si cet aspect de la réflexivité prend forme, alors on va demander de le retirer. Faudra repasser devant EWG car ce détail leur a échappé et on ne veut pas les surprendre. Autre irritant : on se retrouve avec un nouveau cas de Most Vexing Parse avec :

struct A trivially_relocatable(bool(some_constexpr_value)) {}; // augh!

...et à un niveau dans la grammaire où il n'y en avait pas auparavant. Jens Maurer est partisan qu'on lève l'ambiguïté directement dans la grammaire pour ce cas bien précis. En C++, après tout, on voit rarement « struct A » au complet comme type de retour alors que c'est idiomatique de C. Chose intéressante : on n'a pas le problème quand on est final et trivially_relocatable; l'enjeu grammatical n'apparaît que quand il n'y a qu'une seule qualification du genre.

La définition se tient : une classe ne peut être trivially_relocatable si elle a un parent virtuel, un parent qui n'est pas trivially_relocatable ou un attribut d'instance qui n'est pas trivially_relocatable. Si on a un destructeur qui n'est pas = default on n'est pas trivally_relocatable non plus, ce qui fait du sens. On peut être trivially_relocatable implicitement dans certains cas (il y a des règles), mais il faut un mécanisme pour indiquer qu'on ne veut pas l'être (p. ex. : si un objet d'une classe donnée garde un pointeur sur lui-même dans un attribut d'instance ou un truc du genre).

Hubert Tong mentionne le cas d'un union écrit par un individu, qui respecterait toutes les règles de relocalisation triviale mais qui aurait des fonctions associées (p. ex. : un constructeur de copie pour faire « la bonne chose »). Dans un tel cas, il n'y a pas de moyen de faire un opt-in et de faire de cet union un type trivially_relocatable. Jens Maurer pense que c'est peut-être une bonne chose. Hubert Tong pense à un union{ int n; string s; }; où on sait pertinemment que seul le int sera utilisé. Les auteurs semblent étonnés par cette préoccupation, mais c'est le genre de choses que CWG surveille...

(Jens Maurer lance à des gens qui viennent d'entrer – des étudiant(e)s si je ne m'abuse – la phrase « Welcome to Core; as you can see we're dissecting the fine print of the English language » 🙂)

Être trivialement relocalisable exige de ne pas implémenter le constructeur de mouvement soi-même avec l'approche proposée ici. Intéressant. Une note indique même que la relocalisation peut se faire même si le constructeur de mouvement est =default mais privé. La relocalisation est possible même si un type contient des membres d'instance const ou qui sont des références. Évidemment, trivially_relocate() et la mécanique qui l'accompagne est éligible pour Freestanding, et la relocalisation triviale exige des types complets. Il reste d'autres trucs à regarder (ordre des opérations entre la fin de la vie de l'objet d'origine et le début de la vie du nouvel objet par exemple, et dépendance peut-être un peu forte envers l'arithmétique de pointeurs). On reverra cette proposition ultérieurement, cette semaine ou dans le futur.

(brève pause d'après-midi)

P2573R1 – = delete("should have a reason");

Ce titre rappelle [[nodiscard("should have a reason")]], une proposition du passé. Yihe Li nous explique l'intention, soit de permettre de documenter les raisons pour la suppression d'une fonction. Rien de critique donc, mais ça ne nuit pas non plus. Le texte proposé est directement inspiré de celui de static_assert ou de [[nodiscard]].

Outre un détail éditorial, ça devrait passer.

P2963 – Ordering of constraints involving fold expressions

L'intention est de formaliser les règles d'ordonnancement des expressions && et || dans une Fold Expression, en particulier pour choisir une fonction parmi plusieurs sur la base des règles d'Overload Resolution. Un exemple proposé est :

template <std::ranges::bidirectional_range R> void f(R&&); // #1
template <std::ranges::random_access_range R> void f(R&&); // #2
template <std::ranges::bidirectional_range... R> void g(R&&...); // #3
template <std::ranges::random_access_range... R> void g(R&&...); // #4
// ...
f(std::vector{1, 2, 3}); // Ok, appelle #2
g(std::vector{1, 2, 3}); // ambigu avec C++23, appellerait #4 maintenant

Le texte explique comment formaliser (E && ...) et (E || ...) pour une Fold Expression donnée. Hubert Tong mentionne que le cas de la Fold Expression sur un Pack vide ne lui semble pas sainement couvert. Une question se pose : devrait-on faire un Short Circuit dans ce cas, comme on le fait avec les évaluations Runtime? EWG devra examiner cela.

On y travaille une heure environ, puis Hubert Tong propose un exemple douloureux :

template <typename ...T> struct Tuple { };
template <typename T> concept bool P = true;

template <typename T, typename U, typename V, typename X> struct A;

template <typename ...T, typename ...U, typename V, typename X>
requires P<X> || ((P<V> && P<T>) && ...)
void foo(A<Tuple<T ...>, Tuple<U ...>, V, X> *); // #1

template <typename ...T, typename ...U, typename V, typename X>
requires P<X> || ((P<V> || P<U>) || ...)
void foo(A<Tuple<T ...>, Tuple<U ...>, V, X> *); // #2

void bar(A<Tuple<int>, Tuple<>, int, int> *p) { foo(p); }

Ce qui est préoccupant est le traitement du Pack vide dans bar(), ce qui rend #2 préférable à #1 selon lui (plus contraint), cependant les contraintes exprimées par les concepts disent le contraire alors on a un sérieux problème. Jens Maurer suggère qu'on rende de tels cas ambigus. On continue de travailler, mais après une autre heure on cesse, l'auteur ayant suffisamment à faire.

D2809R3 – Trivial infinite loops are not Undefined Behavior

Les modifications demandées ont été faites et EWG a été consulté, mais on nous rapporte un possible irritant avec C qui exprime les règles de la machine abstraite bien différemment de nous. On procède tout de même. Vote samedi.

2856. Initialization with empty braces

On a ceci :

struct A {
   explicit A(int = 10);
   A() = default;   // converting constructor (11.4.8.2 [class.conv.ctor] paragraph 1)
};

A a = {};       // #1, copy-initialization
  
int f(A);
int x = f({});  // #2

A b;            // #3

Intéressant : #1 et #2 passent avec MSVC et EDG, mais sont ambiguës pour gcc et clang. #3 est ambigü selon tous les gros joueurs. On cherche une résolution plus claire à cette situation. La règle proposée est analogue à celle qui prévaut dans les appels de fonctions.

On fait remarquer que ceci empêche le default-initialization de A par quelque syntaxe que ce soit, mais que c'est peut-être sain.

Davis Herring soulève la question de « nous avons traité plus de 50 Core Issues, lesquelles seront des DR? ». On essaie de trouver une stratégie qui fonctionne.

D0876R16 – Fiber Context

Nat Godspeed revient nous accompagner en fin de journée. Jens Maurer souligne des réactions fortes quant au partage des états d'exceptions entre fibres (Nat Godspeed a fait des changements à la prose en conséquence, avec l'aide de Hubert Tong ce qui est... terrifiant!). On examine quelques exemples... Jusqu'à la fin de la journée. C'est perversion après perversion, l'idée étant de s'assurer que EWG comprenne bien les enjeux d'un état d'exception par thread (ce à quoi CWG est opposé).

(on ferme jusqu'à demain; encore une grosse journée devant nous, puis la plénière...)

Jour 4 – 22 mars 2024

Dernière journée pleine avant la plénière et les votes. Je jongle de peine et de misère ce qui s'est produit durant la journée dans les heures entre mon éveil (tardif) et le début des travaux.

P2573 – = delete("should have a reason");

L'auteur a fait les retouches demandées lors de la séance d'hier. C'était une proposition assez simple, basée sur du texte existant. Pour l'essentiel, cette proposition est réglée. Vote samedi.

D2809R3 – Trivial infinite loops are not Undefined Behavior

Les derniers changements faits hier étaient éditoriaux pour l'essentiel. On vérifie que tous les membres de CWG sont d'accord (il nous manquait des joueurs hier).

D1061R8 –Structured Bindings can introduce a Pack

Barry Revzin a fait les ajustements demandés. Une des ajustements est grammaticaux, mais entre en conflit direct pour le choix des noms de productions (et de leur forme) avec P0609 dont nous avons aussi discuté cette semaine. Barry Revzin pense savoir comment résoudre l'imbroglio.

On retravaille la proposition (c'est intéressant et dense). On a un cas où les packs introduits peuvent être vides et on ajoute des exemples pour mettre cette nouveauté en relief. On a des trucs comme :

struct C { int x, y, z; };

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

 ... et comme :

auto g() -> int(&)[4];
auto [a, ...b, c] = g();      // a is the first element of the array, b is a pack containing the second and
                              // third elements, and c is the fourth element
auto& [...e] = g();           // e is a pack referring to the four elements of the array

La définition de implicit template region demeure préoccupante. On passe du temps là-dessus. Des questions surgissent : par exemple, si on est déjà dans un template, est-ce qu'on doit quand même synthétiser une implicit template region? Aussi, il faut harmoniser le vocabulaire et la manière de parler des packs partout dans le texte du standard.

(brève pause; on voulait terminer cette proposition mais c'est très touffu)

Pendant une heure ensuite, on essaie de déterminer dans quelles circonstances le pack introduit serait type-dependent ou value-dependent, avec des exemples un peu acrobatiques. L'exemple à la source des débats dans la proposition est :

struct C { };

void g(...); // #1

template <typename T>
void f() {
    C arr[1];
    auto [...e] = arr;
    g(e...); // calls #1
}

void g(C); // #2

int main() {
    f<int>();
}

... sur la base duquel on construit des types plus élaborés avec des templates variadiques internes pour voir les limites des mots utilisés, en particulier quand les éléments d'un agrégat sont de types différents. Par exemple :

// je n'ai pas eu le temps de saisir les finesses de f() (mais
// g() est void g(...) ou void g(C)) ou du type duquel Nested provient,
// mais ça implique un tuple

template <class T>
void h() {
  auto [...e] = { 1.0f, 2L };
  g(typename decltype(e)::Nested()...);
  f(g(typename decltype(e)::Nested()...));
}

On constate que clarifier le point d'instanciation du template est essentiel. Dans l'exemple où on décompose un C[1] ci-dessus, ce serait quelque part dans main(), sauf si on doit faire une instanciation immédiate. C'est important (ça influence l'appel de ## ou de #2).

On revient sur :

struct C { char j; int l; };

int g() {
    auto [ ... i ] = C{ 'x', 42 }; // #1
    return ( [c = i] () {
        // the local class C is a templated entity because
        // it is defined within the implicit template region
        // created at #1
        struct C {
            int f() requires (sizeof(c) == 1) { return 1; }
            int f() requires (sizeof(c) != 1) { return 2; }
        };
        return C{}.f();
    } () + ...  + 0 );
}

int v = g(); // OK: v == 3

... quelques minutes. John Spicer retravaille l'exemple pour clarifier certains aspects délicats :

struct C { char j; int l; };

void h(...);

template<class ...T>
int g() {
    auto [ ... i ] = C{ 'x', 42 }; // #1
    return ( [c = i] () {
        // The local class L is a templated entity because
        // it is defined within the implicit template region
        // created at #1.
        struct L {
            int f() requires (sizeof(c) == 1) { return 1; }
            int f() requires (sizeof(c) != 1) { return 2; }
        };
    h(c);      // should consistently call the same g, independent of "T"
    h(T());
        return L{}.f();
    } () + ...  + 0 );
}

int v = g(); // OK, v == 3

Barry Revzin décide alors de laisser tomber certains aspects problématiques de l'approche pour le moment (les conséquences seraient... surprenantes pour les programmeuses et les programmeurs). On va le retourner à EWG pour les informer des (importants) changements apportés ce matin, puis on le reverra (peut-être pas aujourd'hui).

P2795 – Erroneous behavior for uninitialized reads

On revient sur celui-ci dans l'espoir d'en arriver à une version prête pour vote samedi. Thomas Köppe a consulté EWG et la présence résiduelle de UB avec bit_cast leur semble acceptable dans les circonstances. LWG a aussi examiné le texte modifié par CWG et nous informe (en souriant) de leur souhait qu'on arrête de toucher à leur texte 🙂.

Jens Maurer fait remarquer qu'avec le texte sur la table, lire un bool invalide ou un pointeur invalide fait « boum! » (on peut espérer que les implémenteurs fassent autre chose, mais on ne les force pas). La proposition donne un traitement différent aux entiers. On va faire avec. Vote samedi.

D3032 – Less transient constexpr allocation

Barry Revzin explique qu'en jouant avec la réflexivité, où plusieurs fonctions retournent des vector, les contraintes actuelles de constexpr deviennent rapidement limitatives. En adaptant la règle pour que la fin de la vie d'un objet construit dans un contexte constexpr se termine à la fin d'un contexte consteval on ouvre quelques portes fort intéressantes. Barry Revzin explique qu'on pourrait aller loin ici, mais que dans certains cas on parle de gros changements au langage alors ce qu'il propose est un petit ajustement pour le moment : changer (avec les mêmes mots à chaque fois) le contexte de validité d'un appel à new / delete / allocate() / deallocate() d'un contexte constexpr (oui, contexte dans un contexte).

Bien fait. Vote samedi.

D2893R3 – Variadic Friends

Il ne manquait essentiellement que le Feature Test Macro et un accord de la part d'EWG pour friend Ts...; maisle vote pris par EWG ne semble pas être ce que nous leur avions demandé de valider. On pense que c'est un problème de communication et on vise toujours un vote pour samedi.

(pause pour le dîner)

On reprend pour constater que cette proposition est probablement en état d'être soumise au vote.

D2786R1 – Trivial Relocatability For C++26

On examine les retouches faites à la proposition, en s'assurant qu'EWG a donné son accord. EWG semble entre autres avoir choisi de maintenir de Most Vexing Parse. On prend soin de construire un exemple très vilain pour s'assurer que toutes et tous ont bien compris l'ampleur des dégâts. On passe de :

struct E trivially_relocatable(bool(my_constexpr_value)) {
}; 

...à cette horreur :

template <auto V> struct X;
struct E trivially_relocatable(bool(my_constexpr_value)) {
   int f();
   int x = 1;
   struct L{};
   X<trivially_relocatable*> *x;
private: // ah, ici on sait que c'est une classe! avant : mystère...
}; 

... pour montrer qu'on peut construire des situations épouvantables où il faut que le compilateur explore très loin pour s'apercevoir qu'il ne faisait pas face à une fonction mais bien à une classe. Mungo Gill explique à un certain moment que sa préférence aurait été de rendre le cas Ill-Formed plutôt que Most Vexing, mais ne voyait pas comment y arriver. Hubert Tong propose une piste pour atteindre cet objectif sans avoir à lire ce qui se trouve entre les accolades. En gros, faudrait dire que struct X trivially_relocatable est toujours un type, qu'il y ait des parenthèses ou pas ensuite. Une solution simple serait de doubler les parenthèses : ((bool(my_constexpr_value)))...

On fait remarquer que même dans un cas comme :

using foo = int;
constexpr int foo = 5;
struct E trivially_relocatable(foo) {
}; 

... il faudra faire un Name Lookup pour foo, mais Jens Maurer nous rappelle que ce n'est pas l'enjeu (le Name Lookup doit être fait dans tous les cas). Mungo Gill signale qu'une solution est d'utiliser une annotation comme [[trivially_relocatable]] mais ce n'est pas le chemin souhaité par cette proposition. On demande s'il est si grave d'avoir un Vexing Parse, mais on rappelle que c'est une nouvelle sorte de Vexing Parse, là où cela n'était pas possible auparavant. C'est ce qui rend ce cas si préoccupant. Plusieurs compilateurs existants risquent de briser si on leur envoie ça au visage. On veut un moyen de ne pas avoir à regarder à l'intérieur des accolades pour faire un choix.

Il existe une règle de désambiguation dans le standard actuel. Elle est légèrement incorrecte, maius de manière bénigne. Le texte proposé la réinvestit, mais dans ce cas les conséquences sont plus graves.

On nous propose un exemple annoncé comme intéressant, mais les débats débordent (ça brasse!) et on n'a jamais l'explication du raisonnement selon lequel celui-ci serait particulièrement amusant. Je le laisse ici pour votre bon plaisir :

constexpr int my_constexpr_value = 0;
struct E trivially_relocatable(bool(my_constexpr_value)) {
   decltype(my_constexpr_value) y = 0;
private: // 
}; 

On passe plus de deux heures à éplucher les détails de la proposition, la plus grande partie étant celle du Vexing Parse novateur bien sûr. Un questionnement intéressant provient de l'interaction entre a relocalisation triviale et les valeurs erronées, mais on convient que l''objet résultant de la relocalisation sera erroné si l'original était erroné (c'est un std::memmov() qui se passera probablement sous la couverture, accompagné d'une mise à jour de la perception selon le compilateur de la vie, démarrée ou pas, des objets).

(brève pause)

Après la pause, on remarque qu'il faudra ajouter des références croisées aux divers endroits où la durée de vie d'un objet peut démarrer, par exemple dans les clauses qui parlent de allocate() et autres mécanismes de la bibliothèque.

D0562R2 – Initialization List Symmetry

Alan Talbot explique que cette proposition remonte à 2017. C'est simple : permettre une virgule terminale dans les listes de membres en préconstruction comme dans les listes de classes parentes, un peu comme on les permet dans les listes d'initialisation.

On se demande si on pourrait vouloir le faire ailleurs : templates, paramètres de fonctions, Structured Bindings... On ne le soumettra pas au vote car il nous manque trop de membres importants cet après-midi (réflexivité dans une autre salle!), mais ça semble prêt. On demande si on peut en faire un DR et ça semble raisonnable. On parle d'un Feature Test Macro, mais c'est pas le genre de mécanisme sur la base duquel on peut choisir d'utiliser ou pas le tout. Parce que le document est présenté en format D plutôt que P, on suggère un meilleur nom car celui-ci ne dit pas vraiment ce que la proposition essaie de porter.

On fait ensuite le tour des Core Issues présentées demain pour vote, dans l'optique de s'assurer qu'elles sont toutes des DR. Je vous passe les détails, mais c'est un travail exigeant sur le plan de la rigueur (on en a traité beaucoup!). Ça prend un peu plus d'une heure

On prend un peu de temps pour planifier les rencontres de CWG à distance, mais je ne peux jamais participer à celles-là (je suis toujours en classe).

Ensuite, on examine des Core Issues qualifiées « Priority 1 »

2765. Address comparisons between potentially non-unique objects during constant evaluation

Que se passe-t-il quand on compare les adresses de deux objets dans un contexte constexpr? Après tout, ils n'ont pas de véritable adresse! Un truc comme :

constexpr bool Ok = "allo" == "allo"; // comparaison d'adresses, pas de contenu

Si on compare des string_view la valeur est true, mais c'est pas ce qu'on fait ici. On se demande ce qu'on devrait exiger : « adresse » différente sur chaque évaluation, par exemple? Ou canonicaliser les littéraux textes pour qu'à même littéral ont ait la même adresse (dans un contexte constexpr seulement)? Il y a des pour et des contre pour chaque approche... Un des enjeux est de savoir ce qui se produit si on a des pointeurs sur « un même littéral » mais dans deux modules distincts.

On converge vers « une valeur différente pour chaque évaluation », même s'il y a une boucle. Mais c'est pas clair que c'est ce qu'on veut : aujourd'hui, les implémentations divergent... Après une heure, on n'a pas de conclusion ferme, et la journée est terminée.

Jour 5 – 23 mars 2024

Je me lève en milieu d'après-midi et, après quelques tâches ménagères, je prends connaissance des sujets soumis pour vote aujourd'hui. Ayant passé ma semaine chez CWG, je suis assez au fait de ce qui est sur la table de ce côté, mais pour les votes proposés par LWG je ne suis pas à jour.

Il y a une note sur un chiffrier partagé par les représentantes et les représentants canadiens à l'effet qu'il y aura un caucus à 7 h 30 heure de Tokyo (18 h 30 heure de Montréal). Je ne vois pas de lien alors je laisse une note à cet effet (j'y serai si je sais où aller!). La rencontre a lieu au moment attendu, et il y a peu d'enjeux « rudes » qui se pointent à l'horizon pour le vote (il y a des enjeux qui s'annoncent pour le proche futur, comme des tensions autour des contrats et du UB et un choix entre état d'exception par-thread ou par-fibre, mais ces deux sujets ne sont pas eu menu des votes cette semaine). Hubert Tong rapporte qu'une des propositions de LWG (inplace_vector<T>) serait impossible à implémenter sans std::uninitialized(), alors que Bryan St-Amour pense que c'est plus un enjeu de Placement New constexpr, mais de part et d'autre on parle d'un enjeu qui peut être résolu pour C++26.

La rencontre démarre avec 15 minutes de délais car les hôtes sur place ont gracieusement fourni des bouchées matinales. John Spicer explique le déroulement de la plénière et rappelle les règles de votation.

Rapports des groups individuels :

Votes pour CWG

Les propositions soumises pour vote sont les suivantes.

1. Accept as Defect Reports and apply the proposed resolutions of all issues in P3196R0 (Core Language Working Group "ready" Issues for the March, 2024 meeting) to the C++ Working Paper

Unanime.

2. Apply the changes in P2748R5 (Disallow Binding a Returned Glvalue to a Temporary) to the C++ Working Paper

Unanime.

3. Accept as a Defect Report and apply the changes in P3106R1 (Clarifying rules for brace elision in aggregate initialization) to the C++ Working Paper, resolving core issue 2149

Unanime.

4. Apply the changes in P0609R3 (Attributes for Structured Bindings) to the C++ Working Paper

Unanime.

5. Accept as a Defect Report and apply the changes in P3034R1 (Module Declarations Shouldn’t be Macros) to the C++ Working Paper

Unanime.

6. Accept as a Defect Report and apply the changes in P2809R3 (Trivial infinite loops are not Undefined Behavior) to the C++ Working Paper

Unanime.

7. Apply the changes in P2795R5 (Erroneous behaviour for uninitialized reads) to the C++ Working Paper

Unanime.

8. Accept as a Defect Report and apply the changes in P3032R1 (Less transient constexpr allocation) to the C++ Working Paper

Hubert Tong dit que la proposition est arrivée tard et que selon lui, le texte n'atteint pas l'objectif. Jens Maurer suggère qu'on retire la proposition dans les circonstances. On discute, et on la remet à plus tard.

9. Apply the changes in P2573R2 (= delete("should have a reason");) to the C++ Working Paper

Unanime.

10. Apply the changes in P2893R3 (Variadic friends) to the C++ Working Paper

Unanime.

Votes pour LWG

Les propositions soumises pour vote sont les suivantes.

1. Apply the changes for all Ready and Tentatively Ready issues in P3180R0 (C++ Standard Library Ready Issues to be moved in Tokyo, Mar. 2024) to the C++ working paper

Unanime.

2. Apply the changes in P2875R4 (Undeprecate polymorphic_allocator::destroy for C++26) to the C++ working paper

Unanime.

3. Apply the changes in P2867R2 (Remove Deprecated strstreams From C++26) to the C++ working paper

On vote : consensus.

4. Apply the changes in P2869R4 (Remove Deprecated shared_ptr Atomic Access APIs from C++26) to the C++ working paper

On vote : consensus.

5. Apply the changes in P2872R3 (Remove wstring_convert From C++26) to the C++ working paper

On vote : consensus.

6. Accept as a Defect Report and apply the changes in P3107R5 (Permit an efficient implementation of std::print) to the C++ working paper

Unanime.

7. Apply the changes in P3142R0 (Printing Blank Lines with println) to the C++ working paper

Unanime.

8. Apply the changes in P2845R8 (Formatting of std::filesystem::path) to the C++ working paper

Unanime.

9. Apply the changes in P0493R5 (Atomic minimum/maximum) to the C++ working paper

Unanime.

10. Apply the changes in P2542R8 (views::concat) to the C++ working paper

Unanime.

11. Apply the changes in P2591R5 (Concatenation of strings and string views) to the C++ working paper

Unanime.

12. Apply the changes in P2248R8 (Enabling list-initialization for algorithms) to the C++ working paper

Unanime.

13. Apply the changes in P2810R4 (is_debugger_present is_replaceable) to the C++ working paper

Unanime.

14. Apply the changes in P1068R11 (Vector API for random number generation) to the C++ working paper

Unanime.

15. Apply the changes in P0843R11 (inplace_vector) to the C++ working paper

Ville Voutilainen dit qu'on a réalisé à la dernière minute que ce n'est pas implémentable strictement sur la base de la bibliothèque standard. Pablo Halpern dit que SG14 utilise beaucoup d'allocateurs, mais que ce conteneur ne les supporte pas. Jonathan Wakely dit que l'enjeu est constexpr : ce conteneur vise le contexte constexpr mais n'est pas implémentable sous cette forme avec le langage actuel. On discute, et on la remet à plus tard.

16. Apply the changes in P2944R3 (Comparisons for reference_wrapper) to the C++ working paper

Unanime.

17. Apply the changes in P2642R6 (Padded mdspan layouts) to the C++ working paper

Unanime.

18. Apply the changes in P3029R1 (Better mdspan's CTAD) to the C++ working paper

Unanime.

On discute ensuite de la logistique en lien avec la prochaine rencontre. Des remerciements bien sentis sont offerts à certaines personnes, en particulier Jens Maurer qui fait en sorte que tout fonctionne et JF Bastien, notre hôte. On examine la question des prochaines rencontres... et c'est la fin d'une grosse semaine!


Valid XHTML 1.0 Transitional

CSS Valide !