À propos de WG21, Sofia 2025 (juin)

Je participe à cette rencontre à distance. La rencontre de cette semaine se tenant à Sofia (Bulgarie), 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 finalisation 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 (et peut-être le trait is_always_exhaustive) alors je vais investir du temps ailleurs aussi.

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 – 16 juin 2025

Je suis arrivé (virtuellement) à la rencontre vers 8 h 45 heure de Sofia (1 h 45 heure locale). Le café était prêt à percoler alors j'ai pu me mettre en marche rapidement 🙂

John Spicer fait l'accueil comme il se doit. On présente le code de conduite, les règles lors des votes, on laisse les nouveaux membres et les invité(e)s se présenter. On présente l'organisation de la semaine et la répartition des groupes d'études comme des groupes de travail dans les diverses salles. C'était essentiellement la routine habituelle, à ceci près qu'on n'a pas de séance de soirée prévue pour le moment (étrange...).

Le gros morceau de la semaine est la finalisation de C++26. On a toutes et tous hâte d'en connaître les contours avec précision.

On se dirige ensuite vers les différentes salles de travail (virtuelles pour moi). Je devrais passer le plus clair de ma semaine chez CWG encore une fois. Les deux propositions que je dois défendre ne semblent pas à l'agenda, alors j'ai contacté les Chairs des groupes visés. Je crois comprendre d'où vient l'imbroglio; si on ne parvient pas à corriger le tir cette semaine, ça ira à novembre, mais je préférerais que les discussions débutent (ou se poursuivent) plus tôt que tard.

On commence les travaux vers 3 h 15 du matin heure de Montréal. C'est le milieu de l'avant-midi à Sofia. Le gros sujet de la semaine chez CWG est P2996 - Reflection for C++ évidemment. CWG a fait du progrès de ce côté lors de téléconférences régulières, mais je ne peux pas assister à celles-ci (j'enseigne en même temps). Les pauses de dîner seront plus courtes cette semaine (du moins de lundi à mercredi) car le dîner des fourni pour les gens qui participent en présence.

Il semble y avoir des enjeux quant à ce qui peut, avec les mots utilisés en ce moment, « échapper » du contexte statique réflexif et « s'infilitrer » dans le monde run-time. Hubert Tong suggère qu'on s'assure d'avoir une discussion sur le contexte d'évaluation.

P2996 – Reflection for C++

On commence là où CWG en est rendu suite aux téléconférences, soit le Expression Splicing. Par exemple :

struct S { static constexpr int a = 1; };
template <typename> struct TCls { static constexpr int b = 2; };

constexpr int c = [:^^S:]::a;                   // [:^^S:] is not an expression

constexpr int d = template [:^^TCls:]<int>::b;  // template [:^^TCls:]<int> is not
                                                // an expression

template <auto V> constexpr int e = [:V:];   // splice-expression
constexpr int f = template [:^^e:]<^^S::a>;  // splice-expression

auto g = typename [:^^int:](42);
  // [:^^int:] forms part of a type, not a splice-expression

Sur le plan grammatical, ça peut être une postfix-expression alors ça peut apparaître à droite des opérateurs . et ->. On essaie de voir comment utiliser l'écriture obj->template membre en indiquant dans quel cas le template fait partie du Splice Expression ou pas. De manière générale, le [ qui débute un Splice Expression peut être pris pour le début d'une notation de tableau et il faut clarifier les règles qui permettent de décider ce que le compilateur voit quand il rencontre ce symbole. On a le défi supplémentaire qu'on peut parler, sur le plan grammatical, de l'entité décrite par la Splice Expression en plus de ce qui est dans le code source plus « traditionnel ».

Quelqu'un fait remarquer que les mots choisis ne permettent pas d'exprimer un enum décrit par un using dans certains contextes. Pas une raison d'arrêter les travaux, mais il faudra un Core Issue éventuellement (ça recoupe Core Issue 2959 et Core Issue 2557). La préoccupation est d'utiliser . en plus de :: pour nommer un enum membre d'une classe. Pour les mots utilisés dans le texte de P2996, certains implémenteurs sont critiques des choix faits (qui ne reflètent pas toujours ce que font les implémentations). L'exemple est :

enum class fruit { orange, apple };
struct S {
  using enum fruit;       // OK, introduces orange and apple into S
};
void f() {
  S s;
  s.orange;           // OK, names fruit::orange
  S::orange;          // OK, names fruit::orange
  s.[:^^fruit::orange:]; // intentionally not going to work
}

Jens Maurer signale que pour le moment, quoi que fassent les implémentations, le code est ill-formed (il n'y a pas de fruit dans S). Ça pourrait bien sûr changer.

Une Splice Expression peut aussi apparaître après un l'opérateur unaire &. C'est pas un gros enjeu, mais ça révèle quelques paragraphes préexistants qui manquent d'amour.

On aborde ensuite l'opérateur de réflexivité ^^ qui produit un std::meta::info. On a une note controversée, mais Jens Maurer explique que l'intention est de permettre des réflexivités non-portables sur des extensions conformes de certains vendeurs.

La règle du Max Munch s'applique :

static_assert(std::meta::is_type(^^int()));  // ^^ applies to the type-id "int()"
template<bool> struct X {};
consteval bool operator<(std::meta::info, X<false>) { return false; }
consteval void g(std::meta::info r, X<false> xv) {
  r == ^^int && true;    // error: ^^ applies to the type-id "int&&"
  r == ^^int & true;     // error: ^^ applies to the type-id "int&"
  r == (^^int) && true;  // OK
  r == ^^int &&&& true;  // error: 'int &&&&' is not a valid type
  ^^X < xv;              // error: reflect-expression whose terminal name is a
                         // template-name is followed by <
  (^^X) < xv;            // OK
  ^^X<true> < xv;        // OK
}

On fait remarquer qu'un exemple (^^X < xv; // error...) est incorrect. On l'enlève.

L'expression ^^:: représente le namespace global.

On frappe certains cas où une règle est en place pour ne pas briser le Name Lookup. On semble d'avis qu'un simple sous-entendu ne suffit pas pour un truc aussi important alors on reformule pour clarifier l'intention. On regarde entre autres les using-declarators dans le contexte d'une classe. Ceci serait donc incorrect :

struct X{ int f(); };
struct Y : X { using X::f; };
consteval void g() { ^^Y::f; }

Pause dîner vers 5 h du matin heure de Montréal. On essaie de s'assurer qu'il n'y ait pas de risque de lecture ambiguë du texte, mais c'est extrêmement abstrait alors c'est délicat.

À la reprise des travaux, on donne les détails des raisons pour lesquelles les using-declarators dans ce contexte sont empoisonnés (je vous passe les détails). Brian Bi pense que l'exemple suivant est mal couvert (voir https://godbolt.org/z/6q9qT9zzz) :

#include <experimental/meta>
struct A {
  using T = int;
};
struct B : A {
};
struct C : A {
  using A::T;
};
struct D : B, C {
  static constexpr std::meta::info i = ^^T;
};

On propose un exemple additionnel :

struct A { int i; }
struct B : A { using A::i; };
struct C : A, B { int i; };
struct D : B, C {};
decltype(D::i) j; // OK, names C::i per [class.member.lookup]/5.1
auto r = ^^D::i; // error: using-declaration is a member of S(i,B),
                 // and it's too poisonous even if it was discarded during the merge of lookup sets

C'est un exemple bien reçu, mais on ajoutera un cas d'héritage virtuel pour clarifier l'enjeu dans le contexte où le problème pourrait survenir.

On travaille pendant longtemps sur la formulation de l'algorithme permettant de déterminer ce que fait exactement ^^e pour une expression e donnée. On ne veut assurément pas de divergences d'implémentation dans un tel cas! On ajuste l'exemple précédent :

struct A { struct S {}; }
struct B : virtual A { using A::S; };
struct C : virtual A, B { struct S {}; };
struct D : B, C {};
D::S s; // OK, names C::S per [class.member.lookup]
auto r = ^^D::S; // OK: result C::S not found through using-declarator

... puis on l'ajuste à nouveau :

struct A { struct S {}; }
struct B : virtual A { using A::S; };
constexpr std::meta::info r1 = ^^B::S; // error: A::S found through using-declarator
struct C : virtual A, B { struct S {}; };
struct D : B, C {};
D::S s; // OK, names C::S per [class.member.lookup]
constexpr std::meta::info r2 = ^^D::S; // OK, result C::S not found through using-declarator

On fait quelques vérifications pour s'assurer qu'on a bien traité, dans la grammaire, le cas des mots clés contextuels. On continue en examinant le cas des id-expressions. Un truc qui me frappe est que pour une fonction f() donnée, ^^f et ^^&f ne donnent pas le même résultat.

On a cet exemple :

template <typename T> void fn() requires (^^T != ^^int);
template <typename T> void fn() requires (^^T == ^^int);
template <typename T> void fn() requires (sizeof(T) == sizeof(int));

constexpr auto a = ^^fn<char>;     // OK
constexpr auto b = ^^fn<int>;      // error: ambiguous

constexpr auto c = ^^std::vector;  // OK

template <typename T>
struct S {
  static constexpr auto r = ^^T;
  using type = T;
};
static_assert(S<int>::r == ^^int);
static_assert(^^S<int>::type != ^^int);

typedef struct X {} Y;
typedef struct Z {} Z;
constexpr auto e = ^^Y;  // OK, represents the type alias Y
constexpr auto f = ^^Z;  // OK, represents the type Z (not the type alias)

struct B { int m; };
struct D : B { using B::m; };
constexpr auto g = ^^D::m;  // error: D::m names a using-declarator

Le point important, naturellement, est la mécanique de comparaison de deux (méta) objets de type std::meta::info. Les règles sont intéressantes :

If both operands are of type std::meta::info, comparison is defined as follows:

On décide de simplifier :

If both operands are of type std::meta::info, they compare equal if and only if both operands

On s'aperçoit ensuite que dans le contexte où ça se situe, on accepte « oui » ils sont pareils, « non » ils ne le sont pas, mais aussi (à cause des pointeurs, par exemple) « le résultat est non-spécifié » ce qu'on ne veut pas ici.

Les règles des objets consteval-only sont clarifiées, par la force des choses :

struct Base { };
struct Derived : Base { std::meta::info r; };

consteval const Base& fn(const Derived& derived) { return derived; }

constexpr auto obj = Derived{.r=^^::}; // OK
constexpr auto const& d = obj; // OK
constexpr auto const& b = fn(obj); // error: not a constant expression
  // because Derived is a consteval-only type but Base is not.

(je dois m'absenter pour une rencontre avec mon éditeur en vue d'un prochain livre; je reviendrai plus tard si possible... Ce fut fait, j'ai pu revenir après la pause de milieu d'après-midi là-bas, vers 8 h 30 à Montréal)

On étend le rôle et la portée de fonctions consteval et on ajoute à la définition de manifestly-constant-evaluated les idées requises pour l'injection de code à la compilation (des injected declarations, chacune avec son synthesized point). Les idées de immediate-escalating-expression et de immediate-function sont aussi ajustées pour tenir compte de la nouvelle réalité. Un monde d'opportunités, vraiment. Sur le plan grammatical, on a des cas limites qui demandent un regard plus attentif, incluant la liste d'intialisations d'un constructeur et le bloc de capture d'une lambda.

Certaines constructions sont manifestement problématiques. Par exemple :

struct S0 {
  consteval {
    std::meta::define_aggregate(^^S0, {});
      // error: scope associated with S0 encloses the consteval block
  }
};

struct S1;
consteval { std::meta::define_aggregate(^^S1, {}); }  // OK

template <std::meta::info R> consteval void tfn1() {
  std::meta::define_aggregate(R, {});
}

struct S2;
consteval { tfn1<^^S2>(); }
  // OK, tfn1<^^S2>() and the declaration of S2 are enclosed by the same scope

template <std::meta::info R> consteval void tfn2() {
  consteval { std::meta::define_aggregate(R, {}); }
}

struct S3;
consteval { tfn2<^^S3>(); }
  // error: function parameter scope of tfn2<^^S3> intervenes between the declaration of S3
  // and the consteval block that produces the injected declaration

template <typename> struct TCls {
  struct S4;
  static void sfn() requires ([] {
    consteval { std::meta::define_aggregate(^^S4, {}); }
    return true;
  }()) { }
};

consteval { TCls<void>::sfn(); }
  // error: TCls<void>::S4 is not enclosed by requires-clause lambda

struct S5;
struct Cls {
  consteval { std::meta::define_aggregate(^^S5, {}); }
    // error: S5 is not enclosed by class Cls
};

struct S6;
consteval { // #1
  struct S7; // local class
  consteval { // #2
    std::meta::define_aggregate(^^S6, {});
      // error: consteval block #1 encloses consteval block #2 but not S6
    std::meta::define_aggregate(^^S7, {});  // OK, consteval block #1 encloses both #2 and S7
  }
}

On constate que le cas S0 fonctionnerait en pratique, du moins pour P2996, mais on pense qu'il faut tout de même l'intredire (le permettre briserait trop de trucs). Idem pour S5.

On arrive enfin au cas du Evaluation Context mentionné d'entrée de jeu. La forme proposée définit un ensemble de points et décrit comment les trouver, mais la formulation semble confuse pour certains d'entre nous alors on la reformule. Une partie de cette logique ressemble à la détermination d'un « comes from » ce qui nous mène à faire des blagues d'INTERCAL. Ça prend plusieurs itérations avant de prendre forme (il y a plusieurs variables dans l'équation; on blague qu'on va finir par manquer de lettres!). Ça nous amène à la fin de la journée...

Jour 1 – 17 juin 2025

Bref dodo (nous avions des travaux à faire à la maison hier), puis je me réinstalle pour la deuxième journée de travail de la semaine. On continue avec P2996.

P2996 – Reflection for C++

On aborde la (toujours délicate) questiin du Linkage avec le code génératif comme avec le code réflexif. Hubert Tong fait remarquer qu'au point d'instanciation on a les accès propres à ce point, et mentionne le lien avec les fonctions inline. Jens Maurer parle de réflexivité sur main() (brrrr...). On discute des nuances entre denotes (nomme) et designates (expose, donne accès dans le code sans nécessairement nommer). On travaille fort pour empêcher la provocation de violations d'ODR. Il y a beaucoup de travail subtil à faire ici...

 On a un exemple avec des modules :

// Translation unit #1:

export module A;
static void f() {}
inline void it() { f(); }          // error: is an exposure of f
static inline void its() { f(); }  // OK
template<int> void g() { its(); }  // OK
template void g<0>();

[...]

inline void h(auto x) { adl(x); }  // OK, but certain specializations are exposures

constexpr auto r1 = ^^g<0>;  // OK
namespace N2 {
static constexpr auto r2 = ^^g<1>;  // OK, r2 is TU-local
}
constexpr auto r3 = ^^r2;         // error: r3 is an exposure of N2::r2

constexpr auto ctx = std::meta::access_context::current();
constexpr auto r4 = std::meta::members_of(^^N2, ctx)[0];
  // error: r4 is also an exposure of N2::r2

// Translation unit #2:
module A;
[...]

On disute de la possibilité de considérer un changement au ODR, qui permettrait de distinguer (par exemple) un int et un alias vers un int, à tout le moins pour le code généré par define_aggregate(). Ce serait... radical.

Vlad Serebrennikov demande que l'on revisite un exemple de la veille dans lequel il y avait de l'héritage virtuel, changeant virtual A pour virtual B car il y a selon lui un problème dans les règles telles qu'écrites. Les règles semblent avoir été brisées pour une vingtaine d'années. Ça semble être Core Issue 39.

On poursuit. Truc amusant : les blocs consteval sont définis comme suit :

For a consteval-block-declaration D, the expression E corresponding to D is:

[] -> void static consteval compound-statement ()

D is equivalent to:

static_assert((E, true));

[ Note 1: The evaluation of the expression corresponding to a consteval-block-declaration can produce injected declarations as side effects ([expr.const]). — end note ]

Il y a de la résistance à cette manoeuvre (certains pensent qu'on devrait simplement définir l'effet directement). On change d'approche et on s'inspire plutôt du texte définissant static_assert() pour définir le texte des blocs consteval. Ça devient des vacuous-declarations.

On examine les changements à typedef... Le texte préexistant est vraiment vieux, et on s'amuse en le relisant. Il y a des conséquences un peu partout, incluant decltype... L'exemple pour decltype est :

const int && foo();
int i;
struct A {double x; };
const A* a = new A();
decltype(foo()) x1 = 17;       // type is const int&&
decltype(i) x2;                // type is int
decltype(a->x) x3;             // type is double
decltype((a->x)) x4 = x3;      // type is const double&
decltype([:^^x1:]) x5 = 18;    // type is const int&&

void f() {
  [](auto ...pack) {
    decltype(pack...[0]) x5 x6;    // type is int
    decltype((pack...[0])) x6 x7;  // type is int&
  }
}

(pause du matin, il est 3 h 15 du matin ici, mais mon corps a besoin d'un peu de repos... Je reviendrai plus tard... vers 8 h 15 heure de Montréal. On travaille toujours sur la réflexivité)

L'exemple discuté lors de mon retour est le suivant :

class Incomplete;
consteval {
  int n = nonstatic_data_members_of(
      define_aggregate(^^Incomplete, {data_member_spec(^^int, {.name="x"})}),
      std::meta::access_context::current()
    ).size();

  Incomplete y;  // error: type of y is a incomplete
}
/* P */

The value of n is 1. The member Incomplete::x members-of-precedes ([meta.reflection.member.queries]) the synthesized point P associated with the injected declaration produced by the call to define_aggregate.

C'est intéressant quand même. Le synthesized point est P, à partir duquel définir y aurait été légal.

On indique qu'un exemple a été ajouté aux tableaux :

extern int x[10];
struct S {
  static int y[10];
};

int x[];              // OK, bound is 10
int S::y[];           // OK, bound is 10

void f() {
  extern int x[];
  int i = sizeof(x);  // error: incomplete object type
}

namespace A { extern int z[3]; }
int A::z[] = {};   // OK, defines an array of 3 elements

On poursuit dans le code génératif et on discute des enjeux d'alignement et de disposition des membres dans le code généré. Les variables membres qui sont des références ou des bitfields demandent une attention particulière, sans surprises. L'espace nommé std::meta contient des fonctions comme size_of(), alignment_of() et offset_of() et on a une note disant « It is possible that while sizeof(char)==size_of(^^char) that sizeof(char&)!=size_of(^^char&). If b represents a direct base class relationship of an empty base class, then size_of(b)>0. ». La raison pour cela est que les références n'ont pas à occuper d'espace en mémoire alors que le layout-associated-type de T& ou de T&& est T* (ce qui ne veut pas dire qu'en pratique, un char& prendrait nécessairement de l'espace dans un programme). On a aussi type_of(), constant_of(), object_of(), etc.

Ça fait réaliser qu'on est en train de retirer un certain degré de liberté des mains du compilateur en définissant formellement des règles qui laissaient de la liberté par le passé... Ça paraît surtout avec offset_of() car on peut décrire la disposition des variables membres d'un objet. Hubert Tong propose une approche descriptive expliquant que les informations obtenues ici portent sur le layout-associated-type. On essaie de s'inspirer de la description des dispositions semblables mais des value representations distinctes des entiers signés et non-signés.

La description d'une variable membre est un cinq-uplet {T,N,A,W,NUA} (type, identifiant, alignement, largeur de bit-field, et un booléen qui, s'il est true, signifie [[no_unique_address]]). Si on cherchait une preuve que [[no_unique_address]] est différent des autres annotations, on l'a ici.

En discutant des règles d'accès, on constate que l'accès n'est plus résolu par nom mais bien par désignation. On discute longtemps pour s'assurer de bien comprendre ce qui se passe et pour éviter de briser quelque chose de fondamental. On examine ensuite la mécanique d'accès aux membres, mais ça révèle des irritants dans le texte préexistant. C'est fou à quel point ce nouveau mécanisme recoupe de nombreux autres mécanismes du Core Language. Plusieurs passages discutant par exemple de l'accès à un membre demandent de décrire ce qui se passe dans le code source et ce qui se passe si on utilise un mécanisme réflexif comme une splice-expression.

(on déborde du temps alloué par une dizaine de minutes; à suivre demain)

Jour 2 – 18 juin 2025

On commence à l'heure.

P2830R12 – Standardized Constexpr Type Ordering

On doit traiter cette proposition tout de suite car elle doit être vue par d'autres groupes par la suite. On parle de remplacer un ordre défini mais non-spécifié de types (offert par std::type_info) par un ordre spécifié et stable à travers les unités de traduction. Cet ordre s'exprimerait à travers le trait std::type_order<T,U>, et il s'exprime à travers les mécanismes de std::strong_ordering (dans <compare>) qui accompagnent operator<=>().

La seule remarque faite ce matin (on a déjà vu cette proposition) est que la Feature-Test Macro devrait être disponible sur plateformes Freestanding. On explique que les Senders-Receivers vont bénéficier de ce trait, alors tant mieux!

On approuve. Ce sera porté par LWG lors des votes de samedi.

P2996 – Reflection for C++

On commence en vérifiant l'application des changements appliqués (à notre demande) par les auteurs au cours de la nuit.

Un truc auquel il faut résister et la tentation de corriger des passages plus vieux qui ont besoin d'amour, mais qui demanderaient un examen approfondi et pour lesquels des changements en apparence mineurs pourraient nuire à la solidité de l'édifice structurel qu'est le standard du langage. Les passages sur le Overload Resolution sont parfois tentants...

Évidemment, à CWG, la clarté (au sens de « absence d'ambiguïtés ») est essentielle. On passe parfois quinze minutes sur la présence ou l'absence d'un « the »...

Le texte qui couvre la réflexivité sur les paramètres avec la valeur par défaut nous demande plus d'une heure. C'est un texte complexe et qui déplace des parties du standard au passage d'une section à une autre. Pour un exemple de ce que ça donne :

namespace A {
  extern "C" void f(int = 5);
}
namespace B {
  extern "C" void f(int = 5);
}

void splice() {
  [:^^A::f:](3);  // OK, default argument was not used for viability
  [:^^A::f:]();   // error: default argument provided by two host scopes
}

using A::f;
using B::f;

void use() {
  f(3);           // OK, default argument was not used for viability
  f();            // error: found default argument twice
}

void g(int);
void use2() {
  void g(int = 7);
  g();       // OK
  [:^^g:](); // error: host scope is block scope
}

void use3() {
  [:^^g:](); // error: insufficient arguments
}

Hubert Tong suggère d'ajouter un exemple montrant un cas où un bloc englobant empoisonnerait la mécanique (voir https://godbolt.org/z/K13ozW7d6) :

void g(int = 7);
void poison() {
  void g(int = 8);
}
void use3() {
  [:^^g:]();
}

On fait remarquer que ce cas est couvert par le texte (c'est l'implémentation expérimentale qui n'est pas à jour). Ceci est par contre intéressant (et fait un peu peur) :

void g(int = 7);
void poison() {
  void g(int = 8);
  g(); // takes 8
  [:^^g:](); // takes 7
}
void use3() {
  g(); // takes 7
  [:^^g:](); // takes 7
}

On discute ensuite de l'idée du mot Precedes : si on importe une déclaration inline d'un autre module, que signifie Precedes? On voit son effet dans les exemples (une déclaration locale à une fonction peut empoisonner ce qui se trouve à l'extérieur); il faudra manifestement retoucher le texte. Daveed Vandevoorde explique qu'on capture des entités, pas des déclarations, et c'est ce qui (à son avis) entraîne une forme de confusion ici.

Vlad Serebrennikov suggère d'autres exemples qu'il estime utiles :

namespace A {
  extern "C" void f(int, int = 5);
  extern "C" void f(int = 6, int);
}
namespace B {
  extern "C" void f(int, int = 7);
}

void use() {
  [:^^A::f:](3, 4);  // OK, default argument was not used for viability
  [:^^A::f:](3);     // error: default argument provided by declarations from two scopes
  [:^^A::f:]();      // OK, default arguments provided by declarations in the scope of A
  
  using A::f;
  using B::f;
  f(3, 4);           // OK, default argument was not used for viability
  f(3);              // error: default argument provided by declaration from two scopes
  f();               // OK, default arguments provided by declarations in the scope of A

  void g(int = 8);
  g();               // OK
  [:^^g:]();         // error: host scope is block scope
}

template <typename... Ts>
int h(int = 3, Ts...);
int i = h<int>();    // error: no default argument for the second parameter

On travaille encore un peu (!) là-dessus. L'exemple final dans le standard s'inspirera de ça.

On passe quelques sections plus légères, avec des changements mineurs et évidents en apparence (ouf!), quand on s'aperçoit qu'il nous faut revenir à offset_of() et aux enjeux de Layout. L'enjeu de « référence dans le code, pointeur en réflexivité » est (je pense) que lors de la réflexivité, on veut des membres qui soient des objets existants. Un des points de discussion est le Padding (est-il le même dans l'objet représentant la réflexivité que dans l'objet inscrit dans le code?), qui découle de l'alignement décrit par alignof() et par align_of().

(brève pause du matin; j'essaie de voir si je peux faire examiner P3442R2 – [[invalidate_dereferencing]] chez EWG cet après-midi)

On poursuit les travaux sur l'alignement dans un contexte réflexif. On examine ensuite les comparaisons sur std::meta::info (on supporte == et != mais pas <=>). Ensuite, on aborde la réflexivité sur les paramètres des templates et sur les spécialisations de templates :

struct X {
  template<std::size_t> X* alloc();
  template<std::size_t> static X* adjust();
};
template<class T> void f(T* p) {
  T* p1 = p->alloc<200>();              // error: < means less than
  T* p2 = p->template alloc<200>();     // OK, < starts template argument list
  T::adjust<100>();                     // error: < means less than
  T::template adjust<100>();            // OK, < starts template argument list

  static constexpr auto r = ^^T::adjust;
  T* p3 = [:r:]<200>();                 // error: < means less than
  T* p4 = template [:r:]<200>();        // OK, < starts template argument list
}

Appliquer une splice-expression en tant que paramètre à un template exigera le recours à des parenthèses :

template<int> struct S { };

constexpr int k = 5;
constexpr auto r = ^^k;
S<[:r:]> s1;      // error: unparenthesized splice expression used as template argument
S<([:r:])> s2;    // OK
S<[:r:] + 1> s3;  // OK

On aborde ensuite le cas de l'équivalence entre deux templates et de ce que ça signifie dans le contexte réflexif. Ça semble simple pour celles et ceux qui n'ont qu'une appréciation « de base » des templates, mais les templates sont une chose d'une complexité inouïe avec des templates de templates (de templates...), des types dépendants, des dépendances envers des valeurs, etc. et il faut s'assurer que le tout soit cohérent.

Hubert Tong propose cet exemple, qui couvre le cas préexistant, et signale qu'on n'avait pas de spécialisation d'alias de templates avant... maintenant (https://godbolt.org/z/Ean7Tv54q) :

template <typename T>
  using Weird = T *;
template <typename T>
  void f(Weird<T>);
void g(int *p) {
  f(p);
}

Pour une splice expression, ça donne (https://godbolt.org/z/55vKKe5ce) :

template <typename T>
  using Weird = T *;
template <typename T>
  void f(typename [:^^Weird:]<T>);
void g(int *p) {
  f(p);
}

On voit poindre le cas où ^^Weird<T> serait un problème grammatical. Hubert Tong propose une autre monstruosité (https://godbolt.org/z/qh8Tern4h) :

template <typename T>
  using Weird = T *;
consteval auto g(void) {
  return ^^typename [:^^Weird:]<int>;
}

Selon lui, l'enjeu est que nous voulons disqualifier la réflexivité sur un alias template, mais il n'existait pas de moyen de les nommer en tant qu'entité par le passé alors nous n'avons pas de texte pour clairement indiquer ce que nous souhaitons disqualifier. On travaille une bonne heure sur une solution.

(on arrête pour le dîner; il est 5 h du matin à Montréal. Je pense aller à EWG cet après-midi juste au cas où je pourrais défendre l'une de mes propositions)

CWG reprend les travaux un peu plus tôt que les autres groupes alors je commence mon « après-midi » à cet endroit. On regarde la grammaire des expressions using en lien avec les splice expressions. Par exemple :

template <class T> T::R f();
template <class T> void f(T::R);   // ill-formed, no diagnostic required: attempt to
                                  // declare a `void` variable template
enum class Enum { A, B, C };

template <class T> struct S {
  using Ptr = PtrTraits<T>::Ptr;  // OK, in a defining-type-id
  using Alias = [:^^int:];        // OK, in a defining-type-id
  T::R f(T::P p) {                // OK, class scope
    return static_cast<T::R>(p);  // OK, type-id of a `static_cast`
  }
  auto g() -> S<T*>::Ptr;         // OK, trailing-return-type
  auto h() -> [:^^S:]<T*>;        // OK, trailing-return-type
  using enum [:^^Enum:];          // OK, using-enum-declarator
};
template void f() {
  void (*pf)(T::X);               // variable `pf` of type `void*` initialized
                                  // with `T::X`
  void g(T::X);                   // error: `T::X` at block scope does not denote
                                  // a type (attempt to declare a `void` variable)
}

(je quitte CWG pour EWG; on cherche un scribe, ce que je fais habituellement quand je suis en personne, ce qui prend quelques minutes)

« P0876 – fiber_context - fibers without scheduler » et « P3620 – Concerns with the proposed addition of fibers to C++ 26 »

Nat Godspeed commence par expliquer ce que sont les fibers, et ce que les fibers apportent en comparaison avec les coroutines. Il donne quelques exemples de cas concrets pour lesquels des fibers seraient plus à propos que des coroutines.

Nat Godspeed présente ensuite le cheminement de P0876 (conditionnellement approuvé par LWG, puis par CWG; on demandait plus d'expérience d'implémentation, ce qui semble être fait désormais).

Matthew Taylor vient quant à lui signaler ce qu'il considère être des problèmes de fond avec les fibers. Il se dit d'avis que les fibers peuvent exposer des états qui seraient autrement Thread-Local, et qu'il y a un risque de se faire un « auto-deadlock ».

Les débats s'ensuivent. Certains font remarquer que les fibers sont déjà une réalité, qu'il y a beaucoup d'expérience pratique avec elles, que les problèmes possibles sont connus et qu'il en va de même pour les solutions. D'autres signalent que bien que les états globaux mutables soient à bien des égards une mauvaise idée, ils existent et nous avons les moyens de les gérer. D'autres signalent que bien qu'il y ait de l'expérience concrète, plusieurs sont négatives et ont semblé inappropriées pour du code assujetti à des contraintes de basse latence... mais d'autres signalent le contraire.

On prend un vote : consensus pour P0876.

P3667 – Extending range-for loop with an expression statement

J. Daniel García présente. Il propose de permettre la forme for(init; auto x : conteneur; inc) pour les cas où on veut un compteur en plus d'une boucle for sur un intervalle. La présentation montre que bien qu'il soit possible de faire quelque chose de semblable avec views::enumerate cette construction est plus limitée dans certains cas (plusieurs cas sont présentés, d'ailleurs).

Les débats suivent. Il y a peu de rétroaction négative, outre peut-être « ceci est-il suffisamment utile pour mériter un nouveau feature? ». La plupart des gens qui réagissent disent voir des applications pertinentes, ou encore en avoir eu besoin récemment.

On prend un vote : consensus pour.

P3439 – Chained comparisons: Safe, correct, efficient

Herb Sutter présente une deuxième fois cette semaine. L'idée est de déprécier les formes incorrectes d'enchaînement d'expressions relationnelles (son souhait est, éventuellement, de permettre if(a<b<c) et de donner un sens mathématique à cette forme, ou à les interdire le cas échéant).

On prend un vote : consensus pour.

Le vote visait C++29, mais le consensus est fort et certains pensent que C++26 devrait être un meilleur véhicule. Le Chair se dit d'avis que ça ne se rendra pas à CWG cette semaine dans le temps que nous avons. Peut-être si ça peut passer comme une modification éditoriale... On va essayer.

P3203 – Implementation defined coroutine extensions

La proposition demande de faire passer de « undefined » à « implementation-defined » le cas d'une spécialisation de std::coroutine_handle<T> pour un certain type T. On ne demande pas autre chose, car ce qui est « implementation-defined » est hors du standard.

On se questionne sur les risques de pessimisation de l'exécution. L'intégration avec le reste de la mécanique soulève la question du contrat minimal à respecter. Les implémentations supposent des choses sur le Layout physique de ces types, ce qui indique un risque si on ne précise pas le contrat.

On vote pour envoyer ceci en tant que DR à LEWG et CWG. Pas de consensus. Le principal problème semble être qu'on souhaite un design plus complet et plus clair avant d'aller de l'avant avec quelque chose qui pourrait contraindre ce que font les implémentations.

(pause d'après-midi)

P2034 – Partially Mutable Lambda Captures

Ryan McDougall présente. L'idée est de donner une granularité plus fine à la qualification const d'une lambda. Le cas d'utilisation visé est un std::function qui soit plus const-correct et qui prenne en charge une lambda. La suggestion est de pouvoir qualifier mutable des objets dans le bloc de capture. On pourrait aussi capturer avec const explicitement et (de manière redondante) qualifier la lambda const, par symétrie.

Les débats suivent. Certains questionnent l'urgence d'introduire ce mécanisme. Alisdair Meredith pense que si on a un membre mutable, il préférerait que l'opérateur () ne soit pas const (l'intention est qu'il soit const). À ma surprise, c'est un débat (selon moi, on veut absolument que l'opérateur () soit const ici).

On prend deux votes.

Encourager la proposition, même sans extensions : consensus pour

Encourager la proposition, avec extensions : consensus pour (un peu plus fort)

P3424 – Define Delete With Throwing Exception Specification

Alisdair Meredith présente. On note que les mots dans operator delete() sont plus forts que pour un destructeur car indiquer noexcept(false) entraîne du comportement indéfini. Son souhait est de ne pas le permettre tout simplement. En ce moment, avec un destructeur noexcept(false) comme on en a parfois pour fins de tests, on a des divergences d'implémentation. Son approche est de commencer par une période de dépréciation.

Quelques débats s'ensuivent. On prend deux votes :

Envoyer la proposition telle quelle à CWG : pas de consensus

Envoyer la proposition en n'interdisant que noexcept(false) à CWG : consensus pour. Ça ira à CWG mais avec mention Needs Revision.

P2843 – Removing UB from the Preprocessor

Alisdair Meredith présente. C'est une bonne idée, mais il y a des subtilités. Entre autres, shall a un sens différent chez LWG (où les clauses se trouvaient) et chez CWG (où les clauses s'en vont) : LWG traite une non-conformité comme du UB alors que CWG les traite comme diagnosticables. Aussi, SG16 a signalé un enjeu avec la possible apparition d'un Form Feed ou d'un Vertical Tab dans un commentaire (à certains endroits, c'est IFNDR!), et a remarqué qu'avec Unicode ces caractères sont des fins de lignes et requièrent un diagnostic!

On débat. Clang fait apparemment du Vectorized Comment Skipping, et ne pourrait pas diagnostiquer ces cas sans changement important. Il se trouve que gcc le fait aussi. Certains signalent que ceci peut créer des risques de sécurité. Certains pensent qu'il nous faudra des données avant de trancher.

On a quelques votes à faire.

Ce qui est dans la proposition est ce qu'on veut, en entier, avec shall qui donne IFNDR lorsqu'on cherche à donner à une macro le nom d'un mot clé : consensus pour

Pour les blancs étranges, on ne diagnostique pas (IFNDR) : consensus contre

Pour les blancs étranges, on diagnostique comme le fait Unicode : pas de consensus

Sais pas que les auteurs vont faire avec ça...

P3747 – Call side return type deduction

Thomas Mejstrik dit avoir initialement visé une proposition pour LWG, mais pense que CWG serait une meilleure cible ici. La technique habituelle avec opérateurs de conversion implicites est proposée mais est limitée aux classes. Il utilise deduce comme mot clé pour les besoins de la cause. Ça permettrait d'écrire :

int n = std::numeric_limits<>::max<deduce>();

... par exemple. Le souhait est d'en en faire un chemin pour réduire la redondance dans le code source. L'intention derrière la proposition est détaillée; évidemment, on ne peut pas utiliser auto et deduce ensemble pour faire de la magie. Un cas d'utilisation intéressant serait l'ajout de paramètres avec valeur par défaut ailleurs qu'à la fin de la liste des paramètres.

Le souhait est de prendre le pouls de la salle.

On se questionne sur les règles qui permettraient de réaliser la déduction. L'approche serait d'avoir une réécriture qui prendrait un paramètre supplémentaire dont le type serait le type de retour. L'enjeu des paramètres par défaut semble sujette à être traitée séparément. On se demande ce qui serait déduit dans les cas où une conversion est requise (il y a quand même les nombreuses règles de promotion arithmétique à considérer). On se questionne sur l'injection de complexité dans le langage, incluant les templates variadiques et la généralisation des paramètres de templates. On se questionne sur le recours à deduce comme un mot clé contextuel (c'est juste un mot de travail). On craint ce type de proposition sans expérience d'implémentation. On suggère un truc comme ? au lieu de deduce.

On vote à main levée. C'est négatif.

P3442 – [[invalidate_dereferencing]] attribute

Je présente. En gros, ça se passe bien, et la rétroaction est positive et pertinente. Je vais devoir parler à Lisa Lippincott (intégration avec les contrats, version C++29 incluant des postconditions non-évaluées comme peut-être une fonction magique consteval bool becomes_invalid(auto&&)), à Bjarne Stroustrup (intégration avec ce qui se fait du côté des profils) et à Herb Sutter (inntegration avec les mécanismes de suivi de lifetime). Rétroaction amusante : j'ai eu au moins un « je préfère autre chose qu'une annotation » mais quelques « j'aime les annotations ».

Vote d'encouragement à poursuivre : consensus pour

En mon absence, CWG a travaillé sur P2996 bien entendu, mais aussi sur P3096 – Function Parameter Reflection in Reflection for C++26, qui aiderait à résoudre des problèmes souvent associés à de la réflexivité dynamique comme la génération de Bindings pour Python ou de pilotes de bases de données, par exemple https://godbolt.org/z/3MoxGTMhd) et P3394 – Annotations for Reflection, qui permettrait d'ajouter des métadonnées à des entités de manière à ce qu'elles ne soient consommables que par réflexivité :

struct [[=0]] S {};  // Okay: Appertains to S.
[[=42]] int f();     // Okay: Appertains to f.
int f[[=0]] ();      // Ditto.
int [[=24]] f();     // Error: Cannot appertain to int.
[[=123]];            // Error: No applicable construct.

Après tout cela, j'ai fait quelques emplettes rapides et je suis tombé d'épuisement.

Jour 3 – 19 juin 2025

De retour chez CWG. On continue sur la réflexivité.

P2996 – Reflection for C++

On examine des changements apportés à type_of(), en particulier la fonction pour exposition seulement has-type() qui est vraie pour bien des trucs mais pas pour un constructeur ou un destructeur.

P3394 – Annotations for Reflection

On examine la grammaire proposée. On peut utiliser plusieurs annotations sur une même entité (p.ex : [[=1, ="yo"sv]]). Le volet bibliothèque inclut is_annotation(), annotations_of() et annotations_of_with_type() (elles retournent des vector<meta::info>) On peut en tirer de l'information avec constant_of() qui est légèrement ajustée en conséquence.

Pour annotations_of(), ça donnerait :

[[=1]] void f();
[[=2, =3]] void g();
void g [[=4]] ();

struct Option { bool value; };

struct C {
    [[=Option{true}]] int a;
    [[=Option{false}]] int b;
};

static_assert(annotations_of(^^f).size() == 1);
static_assert(annotations_of(^^g).size() == 3);
static_assert([: constant_of(annotations_of(^^g)[0]) :] == 2);
static_assert(extract<int>(annotations_of(^^g)[1]) == 3);
static_assert(extract<int>(annotations_of(^^g)[2]) == 4);

static_assert(extract<Option>(annotations_of(^^C::a)[0]).value);
static_assert(!extract<Option>(annotations_of(^^C::b)[0]).value);

template <class T>
  struct [[=42]] D { };

constexpr std::meta::info a1 = annotations_of(^^D<int>)[0];
constexpr std::meta::info a2 = annotations_of(^^D<char>)[0];
static_assert(a1 != a2);
static_assert(constant_of(a1) == constant_of(a2));

[[=1]] int x, y;
static_assert(annotations_of(^^x)[0] == annotations_of(^^y)[0]);

L'idée est semblable pour annotations_of_with_type(). Bien définir l'ordre dans lequel les annotations apparaissent dans le vecteur est compliqué (il y a un ordre local dans une unité de traduction, mais c'est plus complexe en présence de modules... Faut pas oublier qu'on peut annoter ce qu'on veut, incluant des namespaces, et qu'on peut annoter une annotation).

Après un peu plus d'une heure de travail, nous sommes satisfaits de celle-ci. Faudra que EWG et LWG valident quelques-unes de nos suggestions mais ça devrait être ça.

P3096 – Function Parameter Reflection in Reflection for C++26

Un des enjeux ici est que les fonctions peuvent être surchargées, alors la réflexivité se construit sur un Overload Set. En plus, C++ accepte les fonctions templates et leurs spécialisations... Pour définir cette réflexivité sur les fonctions et leurs paramètres, il faut décrire comment construire la réflexivité sur le Overload Set de la fonction.

On a quelque chose comme :

void fun(int);
constexpr std::meta::info r = parameters_of(^^fun)[0];
static_assert(!has_identifier(r));
void fun(int x);
static_assert(has_identifier(r));
void fun(int x);
static_assert(has_identifier(r));
void poison() {
  void fun(int y);
}
static_assert(!has_identifier(r));

Notez que l'information qui peut être glanée de r dans cet exemple change selon le point dans le code source. Notez aussi l'impact de la déclaration de f() locale à poison()... Ouf!

Jens Maurer fait remarquer qu'il y aura peut-être un enjeu dans le cas de fonctions inline avec annotations et qui seraient visibles de divers modules (risque de violation d'ODR).

On continue de regarder les impacts... sur P2996, car on change avec cette proposition ce sur quoi la réflexivité s'applique.

Core Issue 2898 – Clarify implicit conversion sequence from cv T to T

Petit coup d'oeil à un truc déjà traité, et qui sera marqué comme prêt pour vote samedi.

(pause du matin)

P3096 – Function Parameter Reflection in Reflection for C++26

On poursuit. Dans le texte, une réflexivité sur une spécialisation d'un template instancie cette dernière, et a donc un impact sur le programme ce qui est embêtant. On va traiter ça avec une note pour le moment, mais ce sera à régler éventuellement.

On fait ensuite une relecture linéaire du texte entier, pour s'assurer de ne rien avoir échappé. Je note qu'on a has_ellipsis_parameter() mais has_default_argument(), or il semble que ce soit la manière correcte de nommer les paramètres dans les deux cas. On trouve beaucoup de coquilles au cours de cette heure ou à peu près.

On approuve en vue des votes de samedi, sous réserve de petits changements approuvés par EWG puis par LWG.

P3491 – define_static_{string,object,array}

Cette proposition vise à permettre de générer des données qui seront localisées en mémoire statique. On positionne ces objets comme des potentially non-unique objects au même titre que les littéraux textes.

Hubert Tong demande comment on peut faire pour savoir si un pointeur de bytes pointe réellement sur un littéral texte, mais on fait remarquer qu'il faut le savoir car nous sommes dans un contexte consteval (on ne peut pas tricher le système de types). Cela dit, qu'en est-il de :

extern char &c;
is_string_literal(&c);

Vu qu'on ne fait que prendre son adresse, c'est permis. Ouch.

La spécification de is_string_literal(s) dit que ça retourne true si s est un littéral texte ou un de ses sous-objets (ça permet d'utiliser des sous-chaînes).

(pause dîner)

P3096 – Function Parameter Reflection in Reflection for C++26

On continue à travailler sur la clarté des énoncés de la proposition. C'est parfois délicat à formuler car il y a plusieurs niveaux de conditions et de réifications.

Une fois le travail fait, on approuve celle-ci conditionnellement à un dernier tour par les autres groupes. Ce sera porté au vote samedi.

P3491 – define_static_{string,object,array}

On continue à travailler sur la réification des chaînes de caractères statiques. On essaie de trouver un moyen de dire correctement que le texte existe dans le programme (le volet « effet » est imparfait).

Le texte pour synthétiser des tableaux est problématique dans le cas où la séquence variadique de valeurs est de taille zéro. On décide de le traiter comme un cas spécial.

Il est important que les objets réifiés soient un même objet si leurs valeurs sont les mêmes pour être utilisables à titre de NTTP.

On donne notre accord. Ça pourrait être soumis au vote samedi si LWG est d'accord avec les changements apportés par CWG.

P3293 – Splicing a base class subobject

Ce dossier est traité dans une proposition distincte des autres car elle contient des nouveautés qui pourraient être controversées (elle rend possible certains gestes qui ne l'étaient pas sans réflexivité). Un des aspects qu'il ne faut pas oublier est tout l'aspect « accès indirect par un pointeur sur un membre ». Par exemple :

struct B {
  int b;
};
struct D : B {
  int d;
};

constexpr int f() {
  D d = {1, 2};
  B& b = d.[: std::meta::bases_of(^^D, std::meta::access_context::current())[0] :];
  b.b += 10;
  d.[: ^^D::d :] += 1;
  return d.b * d.d;
}
static_assert(f() == 33);

Les bases virtuelles ne semblent pas couvertes par cette proposition, mais c'est un choix délibéré (ça prendrait un échafaudage très complexe).

En retravaillant l'exemple ci-dessus, on montre que la réflexivité permet de régler un problème de classe de base ambiguë, une nouveauté qui risque d'amener un changement à certains messages d'erreurs :

struct B {
  int b;
};
struct C : B {
  int get() const { return b; }
};
struct D : B, C {
  int d;
};

constexpr int f() {
  D d = {1, {}, 2};
  B& b = d.[: std::meta::bases_of(^^D, std::meta::access_context::current())[0] :];
  b.b += 10;
  return 10 * b.b + d.get();
}
static_assert(f() == 110);

Notez la présence des deux B dans le portrait. La réponse est la bonne. Pensez-y...

(j'ai dû manquer le dernier bloc de travail car mon plus jeune enfant, Ludo, avait une cérémonie de graduation au primaire!)

Jour 4 – 20 juin 2025

On commence. Il semble que la dernière partie de la journée hier ait porté sur les Expansion Statements.

P1306 – Expansion Statements

Oh, ça fait longtemps qu'on veut cela! L'idée est que écrire le code à gauche sera équivalent à écrire le code à droite :

Avec un Expansion Statement Sans un Expansion Statement
template <typename... Ts>
  void print_all(Ts... elems) {
    template for (auto elem : {elems...}) {
      std::println("{}", elem);
    }
  }
template <typename... Ts>
  void print_all(Ts... elems) {
    {
      {
        auto elem = elems...[0];
        std::println("{}", elem);
      }

      {
        auto elem = elems...[1];
        std::println("{}", elem);
      }

      {
        auto elem = elems...[2];
        std::println("{}", elem);
      }
    }
  }

Cela dit, ce matin, on débute en revenant sur la réflexivité appliquée aux paramètres de fonctions.

P3096 – Function Parameter Reflection in Reflection for C++26

EWG a été consulté à propos des paramètres rapportés par has_identifier() dans le cas de spécialisations d'une template, mais la section Rationale de la proposition ne dit pas clairement ce qui est attendu encore aujourd'hui. L'un des auteurs explique sa vision, qui sera ce que nous appliquerons pour le moment. Faudra valider à nouveau auprès de EWG pour s'assurer que l'intention de conception demeure respectée.

P1306 – Expansion Statements

Les Expansion Statements étant applicables aux autre boucles for, ils impactent le traitement de break et de continue. De même, un template for est considéré comme un template pour les besoins de la génération de code et des règles qui s'appliquent.

Quelques exemples chouettes :

// exemple A
consteval int f(auto const&... Containers) {
  int result = 0;
  template for (auto const& c : {Containers...}) {  // OK, enumerating expansion statement
    result += c[0];
  }
  return result;
}
constexpr int c1[] = {1, 2, 3};
constexpr int c2[] = {4, 3, 2, 1};
static_assert(f(c1, c2) == 5);

// exemple B
consteval int f() {
  constexpr std::array<int, 3> arr {1, 2, 3};
  int result = 0;
  template for (constexpr int s : arr) {  // OK, iterating expansion statement
    result += sizeof(char[s]);
  }
  return result;
}
static_assert(f() == 6);

// exemple C
struct S { int i; short s; };
consteval long f(S s) {
  long result = 0;
  template for (auto x : s) {  // OK, destructuring expansion statement
    result += sizeof(x);
  }
  return result;
}
static_assert(f(S{}) == sizeof(int) + sizeof(short));

On débat pendant plus d'une heure sur les règles qui permettront de bien définir le point d'instantiation (et on remarque qu'il peut y en avoir plusieurs dans une même unité de traduction; c'est une étrane bestiole!).

Hubert Tong fait remarquer que le cas d'une expansion vide ne semble pas être diagnostiquable sur la base du texte proposé. On retravaille. On a certains débats sur la complexification résultante du texte (certains sont d'avis que le devoir de diagnostiquer est implicite, mais d'autres insistent sur la cohérence interne du texte du standard).

(pause du matin)

Hubert Tong suggère qu'on devrait ajouter un exemple où une lambda apparaît dans la déclaration de la variable servant à itérer, pour montrer que cela génère un nouveau type à chaque fois (ce qui semble être l'intention). Étant donné le temps qui file, on convient de le laisser produire un exemple et de l'insérer sur une base éditoriale.

On fait un dernier tour sur la proposition, et ça semble bon. On la soumettra au vote demain.

P2996 – Reflection for C++

On porte notre regard sur access_context, plus particulièrement sa fonction membre current(). Ce sont des changements post-approbation alors on a quelques gestes administratifs à poser pour avertir les gens qui auraient lu la version approuvée avant que ces personnes ne prennent position en vue du vote de demain. Ça semble beau, mais faudra que LWG donne à nouveau son accord.

Core Issue 3016. Satisfying the syntactic requirements of #include and #embed

Le problème ici est que le texte de #embed est inspiré du texte de #include mais que les règles de validité du contenu inséré ne sont pas les mêmes (#embed n'a pas de requis syntaxiques; on ne fait qu'injecter des bytes).

L'ajustement proposé, bien qu'imparfait aux yeux de certains, convient.

3018. Validity of defined in __has_embed

Truc amusant : est-ce permis? Ça donne le tournis... La proposition est : non.

On demande si C fera la même chose. Jens Maurer dit que JeanHeyde Meneide s'est engagé à écrire une proposition en ce sens.

3019. Restrictions on character sequences in header-names

La spécification de #embed et de #include rend une partie de la description des règles sur les noms de fichiers d'en-tête redondante. On examine une simplification des règles. On n'est pas prêts à traiter celle-là.

3020. Missing specification for __has_cpp_attribute(indeterminate)

Il en manque une. C'est très mineur.

3014. Comma-delimited vs. comma-separated output for #embed

Petite erreur de spécification. On corrige.

3015. Handling of header-names for #include and #embed

Il y a des aberrations ici, par exemple :

#define stdio nosuch
#if __has_embed(<stdio.h>)    // looks for nosuch.h
#embed <stdio.h>              // looks for stdio.h
#endif

C'est un accident (__had_include n'a pas ce problème). Il y a plusieurs autres étrangetés ici, dont le traitement des concaténations de littéraux.

La résolution proposée est pas mal (je m'attendais à pire dans les circonstances). C'est compliqué parce que toute la partie « nommage des fichiers » dépend de l'implémentation, mais que le nommage lui-même est fait à partir des règles du code source. On a aussi des enjeux de conformité avec C, qui devra (s'il souhaite rester synchronisé avec C++ ici, ce qui semble souhaitable) adapter le texte de cette proposition à ses propres usages.

On remarque que le texte tel que proposé impose d'ouvrir le fichier demandé même s'il ne peut pas le faire, ou si les limites de l'implémentation ont été atteintes. On retouche légèrement pour corriger ce détail. On remarque aussi que l'intention quant au comportement à adopter si limits vaut zéro n'est pas claire.

(pause dîner; je dois aller à LEWG cet après-midi au cas où on parviendrait à trouver du temps pour is_always_exhaustive)

P3740R1 – Last chance to fix std::nontype

Note : https://en.cppreference.com/w/cpp/utility/nontype.html

Il s'agit d'un type qui sert à construire des std::function_ref, mais (a) ça peut avoir été remplacé par std::constant_wrapper et (b) le terme non-type template parameter ne fait plus partie du standard ce qui rend le nom... étrange.

Un rappel du rôle de function_ref est fait (c'est un non-owning callable wrapper). C'est modélisé par un union entre un void* et un pointeur de fonction, puis d'un Thunk Pointer. Le type std::nontype aide à faire des function_ref plus légers quand on se limite à des pointeurs de fonctions (le Thunk Pointer pointe sur une lambda sans état qui relaie l'appel à la fonction, l'autre est nul)

Il se trouve que la définition de std::constant_wrapper<T> est presque identique à celle de std::nontype<T>, et que pour function_ref ils sont interchangeables.

On discute d'alternatives au design actuel. On comprend que std::constant_wrapper<T> expose un operator() ce qui fait qu'il se comporte, dans certains cas, presque comme function_ref, mais pourrait surprendre (la valeur de retour est un constant_wrapper<R> pour un type de retour R donné, et on pourrait construire un function_ref avec lui! Quelqu'un montre que remplacer std::nontype par std::constant_wrapper pourrait créer des incohérences dans certains cas.

Un exemple est proposé (https://godbolt.org/z/beTY813fh) :

constexpr int foo(bool x) {
    return x ? 2 : 1;
}
int main() {
    std::move_only_function<int(bool)> r = std::cw<foo>;
    return r(false) + r(true);
}

Cet exemple compile et retourne 3. Ça fonctionne aussi avec un foncteur constexpr.

On demande pourquoi std::nontype n'était pas exposition-only. La réponse est que le code client doit parfois l'utiliser explicitement. On vérifie qu'on a de l'expérience d'implémentation (le changement a été fait sans conséquences néfaste sur une implémentation de référence, du moins sur move_only_function, mais copyable_function n'a pas été testé faute de temps). Note importante : std::constant_wrapper<T> sera porté pour vote demain.

L'option A serait de se débarrasser de std::nontype<T> et d'utiliser std::constant_wrapper<T> à la place. L'option B serait de lui donner un meilleur nom (p. ex. : std::constant_arg<T>). L'option C est de garder le statu quo.

On discute encore et on trouve des problèmes de gestion de durée de vie avec le function_ref proposé pour l'option A.

On vote. Le consensus le plus important est en faveur du renommage de std::nontype<T> en std::constant_arg<T> (en espérant trouver un meilleur nom).

P1789R1 – Library Support for Expansion Statements (Structured Bindings for std::integer_sequence)

On parle de quelque chose qu'on souhaitait par le passé, mais qui ne devient viable qu'avec l'adoption des Expansion Statements ce matin. L'idée est de rendre possible l'itération sur deux « séquences » et d'utiliser les Structured Bindings comme levier. Pour que ça fonctionne, il faut ajouter un get<I>(arg) arg est un integer_sequence<T,Ns...> et faire de même pour tuple_size et tuple_element.

On vote. Consensus pour. On vise C++26 (il est tard, mais l'intention est d'utiliser le vote comme levier pour un NB Comment)

(brève pause de l'après-midi)

P3526R0 – Container Truncation

L'objectif est d'ajouter une fonction membre truncate(nelems) à quelques conteneurs. On vise C++29. Ce service de troncation pourrait être noexcept, contrairement à resize() par exemple. Ça semble tout à fait raisonnable.

Quelqu'un fait remarquer que string pourrait aussi en bénéficier. On fait remarquer que l'on aura une complexité linéaire, autre avantage. On peut aussi supprimer des éléments plus « déplaisants » plus aisément, n'ayant pas à les copier ou à les déplacer. Certains critiquent le fait que cela ajoute des services à nos classes qui en ont déjà beaucoup. On discute de l'impact de passer une taille supérieure au nombre d'éléments (gestion de l'erreur?) et des vertus d'avoir une fonction membre plutôt qu'une fonction non-membre.

On vote. Consensus pour un truncate() (forme à déterminer).

Pour une forme Free Function, on vote. Consensus contre. Eh ben.

P3711R0 – Safer StringViewLike Functions for Replacing char* strings

Note : ceci s'accompagne de P3566R2 – You Shall Not Pass char*

L'idée est de définir un ensemble de fonctions non-membres qui opèrent de manière sécuritaire sur des chaînes de caractères de taille connue à la manière de ce que permet string_view. Il montre que ceci permet de réduire les risques dans le traitement de chaînes de caractères.

Certaines oppositions surviennent quant aux choix de noms (join() semble un peu commun pour le travail à faire, par exemple) et à une partie volontairement unsafe de l'API, qui ne semble pas apporter de réels avantages.

On vote sur l'ajout de fonctions membres ou pas de ce genre. Pas de consensus.

On vote sur l'ajout de join(). Pas de consensus.

Ce sera ça pour cet après-midi. Pas de temps pour la mienne, on a pris beaucoup de temps pour ce qui était sur la table. Reste les votes la nuit prochaine...

Jour 5 – 15 février 2025

Vers 1 h du matin (8 h du matin à Sofia), nous avons tenu une rencontre des délégués canadiens pour partager les informations que nous avons obtenu tout au long de la semaine et nous préparer en cas où un problème demandant un positionnement national surviendrait.

Ensuite, direction plénière. Je suis vraiment très, très fatigué.

Rapports des groupes d'études

SG1: Concurrency and Parallelism Study Group : rencontre brève (moitié de la semaine). On a peu de travail pour nous alimenter en ce moment

SG2: Modules Study Group : inactif cette semaine

SG3: File System Study Group : inactif cette semaine

SG4: Networking Study Group : inactif cette semaine

SG5: Transactional Memory : inactif cette semaine

SG6: Numerics : quelques trucs intéressants!

SG7: Reflection : rencontre lundi matin. Vu deux propositions, les deux ont été relayées à EWG mais aucune n'a eu le consensus requis pour entre dans C++29

SG8: Concepts : inactif cette semaine

SG9: Ranges : deux rencontres, sept propositions couvertes, quatre on été relayées à LEWG. Mercis à Nicolai Josittus qui a été insistant!

SG10: Feature Test : inactif cette semaine

SG12: Undefined and Unspecified Behavior : inactif cette semaine

SG13: I/O : inactif cette semaine

SG14: Low Latency : pas de rencontre cette semaine mais on se rencontre souvent. On a nommé de nouveaux Chairs pour les jeux (Patrice Roy, Guy Davidson), la finance (Bryan St-Amour) et les systèmes embarqués (John McFarlane)

SG15: Tooling : rencontre une demi journée

SG16: Unicode : inactif cette semaine mais on progresse

SG17: EWG Incubator : inactif cette semaine

SG18: LEWG Incubator : deux rencontres, quelques papiers qui reviendront

SG19: Machine Learning : pas de rencontre cette semaine mais on se rencontre souvent. Nouveaux Chairs là aussi. On aimerait que chaque proposition majeure inclue une part de « comment l'enseigner »

SG20: Education : en s'attend à publier un Learning Path pour l'an prochain. Il nous faut plus de participants

SG21: Contracts : inactif cette semaine

SG22: C/C++ Liason : inactif cette semaine

SG23: Safety and Security : on a eu deux rencontres et plusieurs propositions

ABI Group : inactif cette semaine

Admin Group : on souhaite moins de papiers avec un seul auteur, ou avec tos les auteurs provenant de la même entreprise. On avait 24 NB cette semaine!

Rapports des groupes de travail

EWG : ont vu 54 propositions!

LEWG : rencontres toute la semaine et beaucoup de travail! Il semble y avoir une tension à propos de std::task (qui est arrivé tard...). On signale la qualité des prises de notes.

CWG : CWG  a passé l'essentiel de la semaine sur la réflexitivité. On signale un bug fix dans P2966 qui a été réglé alors que la proposition était officiellement acceptée. Aussi, on a réglé deux Core Issues qui seront votées séparément car elles nme sont pas des DR sur C++23.

LWG : très grosse semaine avec revue des propositions sur la réflexivité et améliorations senders/ receivers.

Votes proposés par CWG

Les votes amenés par CWG cette semaine sont les suivants.

1. Accept as Defect Reports and apply the proposed resolutions of all issues except issues 3013, 3014 and 3020 in P3752R0 (Core Language Working Group "ready" Issues for the June, 2025 meeting) to the C++ Working Paper.

Unanime

2. Apply the proposed resolutions of issues 3013, 3014 and 3020 in P3752R0 (Core Language Working Group "ready" Issues for the June, 2025 meeting) to the C++ Working Paper.

Unanime

3. Accept as a Defect Report and apply the changes in P3618R0 (Allow attaching main to the global module) to the C++ Working Paper.

Unanime

4. Apply the changes in P2996R13 (Reflection for C++26) to the C++ Working Paper and accept as Defect Reports core issues 2701 and 3026 resolved thereby.

Vote. Consensus pour. Ouf, gros progrès pour C++! Ovation debout!

5. Apply the changes in P3394R4 (Annotations for Reflection) to the C++ Working Paper.

Vote. Consensus pour

6. Apply the changes in P3293R3 (Splicing a base class subobject) to the C++ Working Paper.

Vote. Consensus pour

7. Apply the changes in P3491R3 (define_static_{string,object,array}) to the C++ Working Paper.

Vote. Consensus pour

8. Apply the changes in P1306R5 (Expansion Statements) to the C++ Working Paper.

Vote. Consensus pour

9. Apply the changes in P3096R12 (Function Parameter Reflection in Reflection for C++26) to the C++ Working Paper.

Vote. Consensus pour

10. Apply the changes in P3533R2 (constexpr virtual inheritance) to the C++ Working Paper.

Unanime

11. Apply the changes in P2843R3 (Preprocessing is never undefined) to the C++ Working Paper.

Unanime

Votes proposés par LWG

Les votes amenés par LWG cette semaine sont les suivants.

1. Apply the changes in P3742R0 (C++ Standard Library Issues to be moved in Sofia, Jun. 2025) to the C++ working paper.

Unanime

2. Apply the changes in P2988R12 (std::optional<T&>) to the C++ working paper.

Vote. Consensus pour.

3. Apply the changes in P3348R4 (C++26 should refer to C23 not C17) to the C++ working paper.

Unanime

4. Apply the changes in P3037R6 (constexpr std::shared_ptr and friends) to the C++ working paper.

Unanime

5. Apply the changes in P3284R4 (write_env and unstoppable Sender Adaptors) to the C++ working paper.

Unanime

6. Apply the changes in P3179R9 (Parallel Range Algorithms) to the C++ working paper.

Unanime

7. Apply the changes in P3709R2 (Reconsider parallel ranges::rotate_copy and ranges::reverse_copy) to the C++ working paper.

Unanime

8. Apply the changes in P3641R0 (Rename std::observable to std::observable_checkpoint, and add a feature-test macro) to the C++ working paper.

Unanime

9. Apply the changes in P3044R2 (sub string_view from string) to the C++ working paper.

Unanime

10. Apply the changes in P2876R3 (Proposal to extend std::simd with more constructors and accessors) to the C++ working paper.

Unanime

11. Apply the changes in P3480R6 (std::simd is a range) to the C++ working paper.

Unanime

12. Apply the changes in P2664R11 (Extend std::simd with permutation API) to the C++ working paper.

Unanime

13. Apply the changes in P3691R1 (Reconsider naming of the namespace for std::simd) to the C++ working paper.

Unanime

14. Apply the changes in P3383R3 (mdspan.at()) to the C++ working paper.

Vote. Consensus pour.

15. Apply the changes in P2927R3 (Inspecting exception_ptr) to the C++ working paper.

Unanime

16. Apply the changes in P3748R0 (Inspecting exception_ptr should be constexpr) to the C++ working paper.

Unanime

17. Apply the changes in P2830R10 (Standardized Constexpr Type Ordering) to the C++ working paper.

Unanime

18. Apply the changes in P3570R2 (optional variants in sender/receiver) to the C++ working paper.

Unanime

19. Apply the changes in P3481R5 (std::execution::bulk() issues) to the C++ working paper.

Unanime

20. Apply the changes in P3433R1 (Allocator Support for Operation States) to the C++ working paper.

Unanime

21. Apply the changes in P3149R11 (async_scope - Creating scopes for non-sequential concurrency) to the C++ working paper.

Unanime

22. Apply the changes in P3682R0 (Remove std::execution::split) to the C++ working paper.

Unanime

23. Apply the changes in P2079R10 (Parallel scheduler) to the C++ working paper.

Unanime

24. Apply the changes in P3557R3 (High-Quality Sender Diagnostics with Constexpr Exceptions) to the C++ working paper.

Unanime

25. Apply the changes in P3560R2 (Error Handling in Reflection) to the C++ working paper.

On demande si CWG a vu cette proposition. Non. C'est embêtant.

Vote. Consensus pour.

(brève pause ici; il est 3 h 20...)

26. Apply the changes in P3503R3 (Make type-erased allocator use in promise and packaged_task consistent) to the C++ working paper.

Unanime

27. Apply the changes in P3008R6 (Atomic floating-point min/max) to the C++ working paper.

Unanime

28. Apply the changes in P3111R8 (Atomic Reduction Operations) to the C++ working paper.

Unanime

29. Apply the changes in P3060R3 (Add std::views::indices(n)) to the C++ working paper.

Unanime

30. Apply the changes in P2319R5 (Prevent path presentation problems) to the C++ working paper.

Unanime

31. Apply the changes in P3223R2 (Making std::istream::ignore less surprising) to the C++ working paper.

Unanime

32. Apply the changes in P2781R9 (std::constant_wrapper) to the C++ working paper.

Vote. Consensus pour.

33. Apply the changes in P3697R1 (Minor additions to C++26 standard library hardening) to the C++ working paper.

Unanime

34. Apply the changes in P3552R3 (Add a Coroutine Task Type) to the C++ working paper.

Le fait que ce soit arrrivé un peu tard rend les gens craintifs (a-t-on manqué quelque chose de critique?). Longues discussions procédurales...

Vote. Consensus pour.

35. Apply the changes in P1317R2 (Remove return type deduction in std::apply) to the C++ working paper.

Vote. Consensus pour.

Votes proposés par WG21

Il y en a un cette semaine.

1. Appoint an editing committee composed of Jonathan Caves, Daniel Kruegler, Nina Ranns, and Tim Song to approve the correctness of the C++ working paper as modified by the motions approved at this meeting, and to direct the Convener to transmit the approved updated working paper for CD ballot.

Vote. Consensus pour.

Autres rapports

Direction group : Michael Wong remercie les membres du comité. Il rappelle la retraite de Howard Hinnant, et dit que deux personnes ont été ajoutées au Direction Group soit Jeff Garland et Paul McKenney. Il dit qu'on souhaite moins de papiers avec un seul auteur, ou avec tous les auteurs provenant de la même entreprise, car ces propositions semblent avoir de la difficulté à obtenir un consensus et à progresser à bon rythme. La vision de ce groupe en vue de C++29 sera publiée incessamment.

Les prochaines rencontres sont à Kona (novembre, 2025), Londres (mars 2026) et à Brno (Université Mendel, Tchéquie, juin 2026). En novembre 2026, on sera probablement dans le Sud de l'Europe, près de la méditerranée, et un retour en Bulgarie est possible en 2027. On fait remarquer l'absence de rencontres en sol américain outre Kona en novembre. C'est volontaire.

ACCU et C++ On Sea seront co-localisées à Folkestone (UK) en juin 2026.

(il est 4 h 39 et je suis épuisé alors dodo...)


Valid XHTML 1.0 Transitional

CSS Valide !