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 :
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.
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...
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.
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)
On commence à l'heure.
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.
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)
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.
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.
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.
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)
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)
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.
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...
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.
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.
De retour chez CWG. On continue sur la réflexivité.
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.
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.
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.
Petit coup d'oeil à un truc déjà traité, et qui sera marqué comme prêt pour vote samedi.
(pause du matin)
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.
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)
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.
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.
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!)
On commence. Il semble que la dernière partie de la journée hier ait porté sur les 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 |
---|---|
|
|
Cela dit, ce matin, on débute en revenant sur la réflexivité appliquée aux paramètres de fonctions.
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.
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.
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.
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.
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.
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à.
Il en manque une. C'est très mineur.
Petite erreur de spécification. On corrige.
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)
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).
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) où 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)
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.
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...
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é.
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!
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.
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
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.
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.
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...)