Nous avons tenu pour la première fois depuis Prague en 2020 une rencontre en personne du WG21 pour la période allant du 6 au 11 février 2023. Cette rencontre était aussi accessible à distance, ce qui m'a permis de participer presque à temps complet (merci à mes employeurs, le Collège Lionel-Groulx et l'Université de Sherbrooke, de m'avoir accomodé encore une fois).
Les principaux thèmes de la rencontre étaient la résolution de NB Comments pour C++ 23, quelques nouveaux mécanismes pour C++ 26 et des travaux importants sur les plans de la sécurité (SG23) et des contrats (SG21).
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 :
C'est ma première participation à distance (n'ayant pas pu participer à Kona en 2022) alors j'ai un peu de préparation à faire. Pour les gens qui participent à distance, la rencontre se tient en partie par Zoom et en partie par d'autres canaux, incluant un système de clavardage nommé Mattermost que j'ai dû installer et configurer. Les gens qui ont participé à distance jusqu'ici se sont dit ravis du support technologique et de la fluidité des travaux, alors je m'attends à ce que tout se passe relativement bien.
Il y a trois heures de décalage horaire entre Montréal et Issaquah, ce qui me permet de démarrer les enfants le matin (faire les lunchs et tout le tralala) mais me transformera en fantôme à la maison en fin d'après-midi et tard en soirée...
Quelques irritants mineurs de démarrage (le lien pour la plénière sur le Wiki de la rencontre était incorrect, mais les échanges par Mattermost ont permis de régler le tout rapidement). John Spicer explique les règles de fonctionnement, le code de conduite, etc. Nous avons 19 NB participants cette semaine. Comme c'est la coutume, les nouvelles participantes et les nouveaux participants sur présentent (il y en a 25!). Nous chercherons à terminer le traitement des NB Comments sur C++ 23 cette semaine (le document de travail est https://wg21.link/n4928).
L'hôtel est un peu petit pour nous alors il nous faut utiliser certaines plages de soirée pour des réunions de SG. Nous essaierons d'éviter les prises de décision durant ces séances tardives car les soirées servent traditionnellement à des discussions plus informelles en vue du futur.
---
Pour aujourd'hui, je compte passer l'essentiel de mon temps avec CWG, qui me manque beaucoup. Demain, je devrai passer une partie de mon temps à LEWG pour y présenter un enjeu technique sur un nouveau conteneur (std::hive, autrefois std::colony) conçu par mon ami Matthew Bentley (c'est un dossier sur lequel on travaille depuis presque dix ans!).
On y va.
On convient que c'est un dossier clos (le Wording proposé fonctionne)
Il se peut qu'on ait une mise à jour de cette proposition; sinon, ce sera un rejet faute de consensus pour le changement (on s'attend à plusieurs itérations s'il y a une proposition).
Devrait être corrigé par p2736 (que nous verrons plus tard aujourd'hui)
SG16 recommande de rejeter ceci. Ce sera aussi notre position.
Il y a une proposition à ce sujet.
Voyons voir si nous pouvons faire quelques ajustements en vue de C++ 23.
Ça semble banal (mettre à jour la valeur de cette macro)
Semble prêt
Semble prêt
Ceci est actuellement ambigu, et on propose d'ajuster les règles pour préférer 1 :
template <class T, size_t N> void foo(T (&)[N]); // 1
template <class T> void foo(T *t); // 2
// ...
int arr[3]{1, 2, 3};
foo(arr);
Hubert Tong : il se peut que ce soit un DR sur C++ 23, et je ne vois pas d'information quant aux divergences d'implémentation en ce moment.
Jason Merill note que c'est actuellement ambigu alors ça ne brisera pas de code existant
Résolution : Ready (DR sur C++ 23) (voir plus bas, il y
a eu des changements depuis)
C++ ne permet pas d'objets de type void, mais C permet des déclarations extern de type void. Notre intention est de rendre ces déclarations illégales en C++ :
int f(), x; // OK, function declaration for f and object declaration for x
extern void g(), // OK, function declaration for g
y; // error: void is not an object type
PR : pourquoi C supporte-t-il ceci?
Aaron Ballman : pas clair (le traitement des qualifications cv est parfois quelque peu confus en C)
Shafik Yaghmour propose https://godbolt.org/z/h5zKf98sd qui montre qu'il n'y a pas de divergence d'implémentation (tous les compilateurs le rejettent).
Résolution : Ready (DR sur C++ 23)
On fait des ajustements éditoriaux mineurs
Il y a divergence dans le traitement des fonctions membres static et non-static :
extern "C" {
struct A {
static void f();
constexpr static void (*p)()=f; // error: must point to a function whose type has C language linkage
};
}
L'ajustement proposé rend CWG heureux
Résolution : Ready (DR sur C++ 23)
On a un exemple et du texte pour interdire ceci :
template<typename T> concept C = requires (T a) {
requires sizeof(a) == 4; // OK
requires a == 0; // error: evaluation of a constraint variable
};
... mais c'est inutile car a n'est pas utilisable dans un contexte constexpr. On peut supprimer le texte redondant
Résolution : Ready (DR sur C++ 23)
L'objet de celle-ci est le texte qui discute de la représentation en mémoire des objets, qui semble incorrectement décrire les champs de bits. Un peu de travail semble requis pour que les mots utilisés soient les bons, alors on y reviendra plus tard cette semaine
Il semble qu'un destructeur dans un contexte constexpr qui ne soit pas appelé soit techniquement du comportement indéfini :
#include <memory>
constexpr int test_basic_life_p5() {
class guard_t {
int &ref_;
public:
explicit constexpr guard_t(int &i) : ref_{i} {}
constexpr ~guard_t() { ref_ = 42; }
};
int result = 0;
auto alloc = std::allocator<guard_t>{};
auto pguard = alloc.allocate(1);
std::construct_at(pguard, result);
// std::destroy_at(pguard);
alloc.deallocate(pguard, 1);
return result; // value depends on destructor execution
}
int main() {
constexpr auto v = test_basic_life_p5();
return v;
}
La proposition semble correcte.
Résolution : Ready (DR sur C++ 23)
C'est subtil, mais les mots existants rendent ceci ambigu plutôt que de mener à f(B) :
struct A{
A(int) = delete;
};
struct B{
B(int) {}
};
void fun(A); // #1
void fun(B); // #2
int main() {
fun(0); // #3
}
On doit réfléchir un peu à celle-ci
On parle d'une amélioration à la terminologie.
Résolution : Ready (DR sur C++ 23)
Quelle horreur : le texte actuel permet (par omission) d'écrire :
enum E { e, e };
On corrige ça.
Résolution : Ready (DR sur C++ 23)
On semble trop permissifs quant aux qualifications de paramètres (la production grammaticale acceptée semble trop large; elle accepte static, extern et d'autres étrangetés). Cette proposition suggère de prendre une production similaire mais moins vaste
Plusieurs semblent surpris de ce qui est permis... Hubert Tong note que la proposition d'ajustement est probablement trop vaste elle aussi. Jens Maurer se demande si les restrictions sémantiques ne couvrent pas déjà nos arrières. Davis Herring note qu'on a une contrainte indirecte qui parle de la possibilité d'initialiser l'objet. Christof Meerwaald note que auto n'est pas correctement couvert par le texte
On n'est clairement pas prêts à accepter ceci. Peut-être plus tard cette semaine
La terminologie actuelle accepte ceci :
struct MyType {
int i;
double d;
std::strong_ordering operator<=>(const MyType &c) const = default;
};
... mais ça ne fait pas de sens car double n'est pas strongly ordered.
Jason Merill se demande si le problème vient de la définition du mot usable. Davis Herring précise que le code est usable, mais que l'échec vient plus tard quand le type de retour est pris en considération
La proposition de RS est de rendre ceci équivalent à une fonction =delete. PR : Would the fact that =default means `deleted` in this case be something that occurs in some other «usable» contexts too or do we foresee it to be limited to spaceship? Jason Merill et Davis Herring discutent du sens de usable qui semble omettre le type de retour
La proposition suggère de considérer static_cast<R>(a<=>b) pour évaluer le volet usable.
Jason Merill investigue les mots utilisés pour les expressions requires
Davis Herring jongle avec Well-Formed when treated as an unevaluated operand (tiré des mots de LWG)
On retravaille et on en reparle peut-être plus tard cette semaine
Ceci est interdit, mais peut être permis dans des contextes clos (les mots actuels ne donnent pas cet effet) :
struct A {
int n;
consteval A() {}
};
constexpr A a; // implementations reject
Résolution : Ready (DR sur C++ 20)
(pause de dîner à Issaquah; on reprend dans une heure)
Au retour de la pause, Corentin Jabot n'est pas immédiatement disponible pour présenter p2736 alors nous poursuivons.
Jason Merill signale l'avoir implémenté sur l'heure du dîner et avoir constaté un bris de certains de ces tests, en particulier certains T& (le Core Issue semblait viser spécifiquement une référence sur un tableau, mais le texte accepte des références dans un sens trop large). Les cas de paramètres T& (ou pire : const T&) sont extrêmement fréquents alors il faut faire attention. On cherche une formule...
On revient sur la décision, il faut encore travailler un peu. Pas Ready pour le moment, mais bien Open, priorité 2 (on sait ce qu'on veut, on ne sait pas comment le dire).
Il n'est pas clair si une fonction =default qui est consteval est aussi une fonction constexpr correcte :
template <typename Ty>
struct A {
Ty n;
consteval A() {}
};
template <typename Ty>
struct B {
Ty n;
consteval B() = default;
};
A<int> a;
B<int> b;
Dans les deux cas, a et b, le code devrait refuser de compiler car n n'est pas initialisée, mais il y a divergence entre les implémentations...
La terminologie proposée ajoute le terme constexpr-suitable et le définit. On se demande si on vise C++ 23 ou C++ 26. Roger Orr suggère de le résoudre tout de suite dû aux divergences d'implémentation (on vérifie et les divergences semblent disparues depuis le signalement initial), Hubert Tong signale que la divergence existe encore sur les raisons du rejet. Shafik Yaghmour constate que gcc accepte b : https://godbolt.org/z/cK6vh8rPG
Résolution : Ready (DR sur C++ 23)
Ceci recoupe Deducing this et corrige un bogue de spécification
Résolution : Ready (pas un DR, mais destiné à C++ 23)
Le texte existant était parfois confus sur le sens précis de ces termes. L'ajustement définit clairement templated function, templated class et templated variable
Résolution : Ready (DR sur C++ 23)
Ceci semble aussi un petit correctif de bogue terminologique, qui clarifie l'intention dans le cas des fonctions membres
Résolution : Ready (pas un DR, mais destiné à C++ 23)
Simple mais efficace, une clarification
Résolution : Ready (DR sur C++ 23)
La proposition améliore le texte, mais impacte la grammaire alors on la regarde de plus près
Résolution : Ready (pas un DR, mais destiné à C++ 23)
Mike Miller a retravaillé le texte de celle-ci. Bon travail
Résolution : Tentatively Ready (on va viser C++ 26 au cas où on aurait échappé quelque chose de subtil étant donné la portée de la définition)
En attendant Corentin Jabot, on examine des cas de Drafting qui ont été soumis depuis la dernière rencontre
Certaines définitions clés omettaient de mentionner quand certaines fonctions spéciales des union / std::variant et des tableaux étaient définies ou supprimées. Jason Merill a proposé quelque chose d'intéressant mais de difficile à analyser. Après un certain temps, on décide de le retravailler un peu
Celle-ci porte sur les promotions arithmétiques et le traitement insuffisant des champs de bits, mais l'auteur n'est pas disponible pour en parler avec nous. On va traiter le dossier plus tard
On continue...
Le texte dit « Non-static data member initializers get the same late parsing as member functions and default arguments, but are they also instantiated as needed like them? And when is their validity checked? ». Celle-ci a été discutée sommairement en novembre, mais date de... 2011. Il semblerait que c'est résolu en partie par Core Issue 2631 qui décrit les règles d'évaluation des valeurs des paramètres par défaut (ceci devient subtil quand on a consteval et std::source_location).
On le considérera clos en soi, et on gardera les enjeux associés aux classes imbriquées dans le contexte de Core Issue 1890.
Celle-ci a été discutée chez EWG. On a des règles qui empêchent l'initialisation dynamique de constinit, mais aussi d'autres qui permettent au compilateur de réaliser une initialisation dynamique comme une initialisation statique dans certains contextes. La proposition est une interdiction de faire magiquement fonctionner constinit (ce qui réduit les probabilités de divergences d'implémentation). On a des choix terminologiques inhabituels ici (on parle dans l'hypothétique, « ... même si l'implémentation devait réaliser l'optimisation... »)
On va la laisser sur le brûleur quelques jours et en reparler.
Celle-ci porte sur les promotions arithmétiques et le traitement insuffisant des champs de bits. Brian Bi discute de l'intérêt de rapprocher C et C++ dans ce cas-ci. Il explique la procédure appliquée en pratique par gcc, plus proche de C++ que de C. Jens Maurer rappelle que CWG est souvent sur les frontières du langage et tend à aller là où les compilateurs ne vont pas encore. L'enjeu est de savoir dans quel type un champ de bit est promu lors d'une promotion arithmétique (il y a des coins obscurs...). Brian Bi précise que les problèmes sont plus importants si le type du champ de bits est plus grand que int, et nous rappelle qu'il existe des champs de bits sur des énumérations : https://godbolt.org/z/h1ErzoWe6
(petite pause de 15 h 15 à 15 h 30 heure d'Issaquah; l'horaire est très rigide durant ces rencontres, sinon c'est impossible à gérer)
Chose amusante : dans le cas des promotions des champs de bits sur des énumérations, le type sous-jacent est considéré, mais seulement une fois que toutes les autres options ont été essayées.
Brian Bi suggère qu'un DR soit soumis à C. Jens Maurer recommande de passer par SG22, notre liaison officielle avec ce langage.
Un classique : copier un union est un std::memcpy() ce qui implique copier des bits indéterminés s'il y a du padding, ce qui est interdit dans un contexte constexpr. La proposition suggère de le permettre si l'objet source de l'initialisation n'a pas de sous-objets de valeur indéterminée.
Hubert Tong demande si constexpr implique d'offrir un espace d'entreposage. Pas clair que ce soit la clé.
Jason Merill suggère un ajustement terminologique pour clarifier l'intention. Jens Maurer va retravailler le texte et nous revenir.
On a plusieurs options (écrites à la main, générées par défaut, réécrites dû à operator<=>()) et dans certains cas ça cause des surprises (l'exemple qui suit n'est pas le seul relevé dans le signalement) :
#include <compare>
enum class E : int {
Lo = 0,
Hi = 1
};
constexpr auto operator<=>(E lhs, E rhs) -> std::strong_ordering {
return (int)rhs <=> (int)lhs;
}
// everybody agrees this is true
static_assert((E::Lo <=> E::Hi) == std::strong_ordering::greater);
// gcc rejects this, msvc and clang accept
static_assert(E::Lo > E::Hi); // #1
L'enjeu est de bien indiquer les fonctions candidates considérées (ici, la réécriture de operator>(E,E) à partir de operator<=>(E,E) ne découle pas du texte).
On y reviendra cette semaine
L'enjeu est que ceci ne compile pas actuellement :
template <typename T, std::size_t N>
struct A {
T array[N];
};
A a = { "meow" };
... car le littéral "meow" (un const T(&)[N]) ne tombe pas dans les cases couvertes par le texte. La proposition sur la table semble convenir... Tentatively Ready pour le moment
C'est un enjeu de variable membre initialisée par défaut qui nuisait à la qualité de constexpr en 2011, mais Hubert Tong signale que constexpr est tellement puissant aujourd'hui que c'est devenu NAD. On vérifie et on y revient.
La description de cette clause est insuffisamment précise dans le texte actuel (il peut y avoir des parameter-declaration-clause vides ou pas à plusieurs endroits). On travaille plusieurs versions du texte pour en arriver à quelque chose de pas mal. Tentatively Ready pour le moment
Le texte ne semble pas couvrir clairement les paramètres par défaut des fonctions templates. L'ajustement (un mot!) est accepté. Tentatively Ready pour le moment
Ceci ne compile pas dû au moment où l'énumération est considérée comme ayant été spécifiée (l'ajustement proposé est grammatical) :
template<typename T> struct S { typedef char I; };
enum E: S<E>::I { e }; // Implementations say E is undeclared in S<E>
Tentatively Ready pour le moment
Cette production grammaticale est beaucoup trop vaste pour la zone où elle est utilisée (elle inclut entre autres les paramètres par défaut du template). Il faut la contraindre. La proposition est d'ajouter des exclusions; la question est de savoir si c'est la bonne approche, et si on a exclut tous les cas qui devaient l'être.
On discute et on n'est pas satisfaits de ce qui est sur la table pour le moment. Encore un peu de travail...
Le premier dossier autre qu'un traitement de NB Comment au menu est celui de la conformité de C++ dans la manière dont nous référons au standard d'Unicode, et en particulier celui de la conformité entre ISO 10646 et Unicode. Un peu de rodage pour l'audio en début de séance, mais ça se règle rapidement (respect pour l'équipe technique!). La proposition, en gros, est de référer au standard Unicode plutôt qu'à ISO 10646, ce qui est inhabituel pour un standard ISO.
(j'ai manqué quelques minutes, mais à mon retour un examine les ajouts, les suppressions et les suggestions « novatrices » de cette proposition). Une proposition surprenante est de référencer systématiquement https://www.unicode.org/versions/latest/ un peu comme si nous étions toujours « magiquement » à jour
Hubert Tong signale que nous ne sommes pas en mesure de nous tenir à jour avec UAX#31 en pratique. Il signale aussi que nous n'avons plus la correspondance traditionnelle entre deux représentations de caractères que le texte existant avait, mais que le nouveau texte proposé semble ne pas en tenir compte. Il propose un ajustement :
« The "decoded to produce a sequence of [ ... ] scalar values that constitutes the sequence of elements of the translation character set" wording no longer works as-is because it is the wording equivalent of reinterpret_cast<const TranslationCharacters*>(ScalarValues). Suggestion: "decoded to produce a sequence of Unicode scalar values. A sequence of translation character set elements is then formed by mapping each Unicode scalar value to the corresponding translation character set element." »
...qui tient entre autres compte du fait que les caractères Unicode sont clairement des entiers mais ne sont pas clairement des caractères.
On travaille ensuite le texte pour qu'il soit grammaticalement bien formé et qu'il gagne en clarté. CWG prend soin de la langue...
On ferme vers 17 h 30 heure d'Issaquah (20 h 30 heure de Montréal). Il y a une séance de soirée sur les Ranges à 19 h (22 h ici), je vais essayer d'y participer si je suis encore assez éveillé pour cela (note de dernière minute : Inbal Levi nous a informé que SG9 a eu le temps de traiter toutes les propositions sur la table aujourd'hui et que la séance de soirée est annulée).
J'ai un empêchement ce matin, alors j'ai manqué les premières 30 minutes de rencontre du comité, mais je me suis joint à CWG dès midi (heure de Montréal; 9 h heure d'Issaquah). À mon arrivée, on travaillait sur Core Issue 2658.
Hubert Tong propose une formulation qui fait en sorte que dans un contexte constexpr, la copie (ou le mouvement) d'un union copie (ou déplace) le membre actif seulement (s'il y en a un).
Tentatively Ready pour le moment
Christof Meerwaald a essayé la terminologie que nous envisagions avec son compilateur et... ça ne fait pas ce qu'on veut. L'enjeu est un cas de CTAD avec conversions de const char(&)[N] vers unsigned char[N], sans spécifier N. On jongle avec l'idée de rendre l'exemple incorrect (ajuster le commentaire pour indiquer que le code proposé ne compile pas). Ce qui est embêtant est que le code fonctionne sans CTAD... C'est ce qu'on décide de faire en fin de compte (rendre la correspondance d'une référence sur un char[N] à une référence à un unsigned char[N] illégale lors de la déduction).
C'est un sujet assez profond, qui demande réflexion.
On discute ensuite d'un dossier qui recoupe EWG, soit le cas d'une utilisation de std::source_location() dans certains contextes comme une valeur par défaut de paramètre dans une fonction inline et consteval ou encore :
#include <source_location>
inline char *f() {
static char array[std::source_location::current().line()];
return array;
}
... Ça crée le chaos, et personne ne sait quoi faire avec ça. Jason Merill va porter la lecture de CWG devant EWG.
Oh boy, on a un problème pervers :
double operator""_Bq(long double); // OK: does not use the reserved identifier _Bq (5.10)
double operator"" _Bq(long double); // ill-formed, no diagnostic required:
// uses the reserved identifier _Bq (5.10)
... car le nom _Bq qui débute par _ suivi d'une majuscule est techniquement un nom réservé par [lex.name] (c'est l'espace qui pose problème). On se demande quel serait le bon chemin pour résoudre ceci (ça impacte l'analyse lexicale!), mais on oblige le préfixe _ pour les noms suffixes de littéraux maison! Il faudra interdire formellement l'espace a cet endroit (triste...). Hubert Tong et Aaron Ballman suggèrent de retourner le dossier à EWG car la résolution ne permet pas aux implémentations de réaliser des extensions, ce qui est important dans certains cas. En attendant, on ajuste l'exemple qui contient des espaces en n'en laissant qu'un seul (explictant que c'est désormais déprécié).
Pour fins d'extensions, on explore l'idée de réserver les littéraux dont les noms de suffixes débutent par __ (deux soulignements). Ça impacterait [usrlit.suffix].
(brève pause de 15 minutes)
Shafik Yaghmour suggère qu'on ajoute quelques exemples dans cette section pour clarifier les changements importants au processus de Name Resolution apportés par Davis Herring ces dernières années (Shafik Yaghmour rapporte des cas de divergences parmi les implémentations). Un des enjeux est qu'une déclaration using est bel et bien une déclaration, pas une définition, et peut donc être répétée à loisir (ce qu'il faut éviter, c'est de donner deux sens différents à un même nom).
Il se trouve qu'on a un exemple incorrect dans le standard selon l'intention de Davis Herring , et que deux paragraphes se contredisent pour le cas où une classe D a deux parents A et B qui ont tous deux un parent nommé C qui a lui-même un membre nommé i et où D fait using A::i; et using B::i; (ce qui est incorrect, mais c'est pas clair dans le texte du standard).
On examine [namespace.udecl#10] en profondeur. C'est un sujet extrêmement complexe, et Davis Herring essaie de raisonner à travers le tout, mais il se peut qu'on ait à y revenir. Davis Herring pense que [class.member.lookup] montre ce que l'algorithme est supposé faire. On gratte... le problème semble remonter à C++ 98. On a droit à des perles telles que « ce compilateur fait ce qu'on voulait, sauf qu'on n'est pas certains que ce soit ce qu'on veut ».
On finit par en arriver à un exemple simplifié
Tentatively Ready pour le moment (après plus d'une heure trente de réflexion profonde)
BB propose une nouvelle version de la résolution proposée. Ça semble bien reçu.
Tentatively Ready pour le moment
Ce programme C++ 14 était légal mais est brisé par C++ 17 :
// x.hh
struct X {
static const int x;
};
// TU 1
#include "x.hh"
constexpr int X::x{};
// TU 2
#include "x.hh"
int main() { return !&X::x; }
Davis Herring pense que ça devrait être légal, donc que le inline implicite ne s'applique pas ici. La résolution convient.
Tentatively Ready pour le moment
(pause de dîner à Issaquah; je fais une brève sieste car j'ai le cerveau en surchauffe)
On revient sur ce document, qui a changé depuis hier. Les changements demandés semblent être présents, et on regarde les menus détails de format comme les marqueurs de fin de note quand il y en a.
Jens Maurer explique le chemin qu'a suivi cette proposition (importante à mes yeux), et explique aussi pourquoi il est pertinent de la revisiter aujourd'hui (certains ajustements terminologiques ont eu lieu depuis l'examen précédent de la proposition). On fait quelques vérifications sémantiques, mais ça semble solide.
Une intéressante ici : dans le passage à Unicode, particulièrement UTF-8, que faire des littéraux non-évalués dans static_assert(message), [[deprecated(message)]], _Pragma(), asm, littéraux maison, etc.? La proposition vise à restreindre ces littéraux à l'encodage de la machine.
Truc intéressant : les métacaractères non-standards étaient permis auparavant mais ne le seront plus avec ce changement (certains sont étonnés de cette décision qui semble venir de EWG). L'exemple qui vient en tête serait un programme qui souhaite que les messages d'erreur deviennent colorés en rouge; on nous rapporte que EWG a spécifiquement demandé de ne pas supporter ces extensions. On discute... Dans le cas des _Pragma, les restrictions risquent de briser les choix faits par certains compilateurs. On remarque que le processus de destringizing appliqué par _Pragma peut mener à des chaînes malformées (on note que _Pragma et #pragma ont un comportement différent quant aux métacaractères d'échappement non-standards).
On remarque que [cpp.line] qui définit le texte qui peut suivre #line est suspect, mais les conséquences d'un changement ici sont difficiles à mesurer alors on traitera le dossier séparément à travers une Core Issue (cela touche WG14 aussi, entre autres raisons). Si c'est un bogue, il a près de 50 ans... Ça fait peur!
Après un examen plus approfondi, le caractère implementation-defined de _Pragma nous fait suggérer de ne pas le contraindre (ça impacterait la compatibilité avec C en particulier), du moins pour le moment (aussi, le toucher sans toucher #pragma est préoccupant). Il faudra l'examiner avec un Core Issue cependant. L'auteur explique que ce qui est le plus important est d'éviter du texte avec des préfixes. Hubert Tong suggère d'envoyer un NB Comment à C aussi, et nous apprend qu'il semble probable que la prochaine version de C soit non pas C23 mais bien C24 (trop de NB Comments à traiter).
Un petit bogue dans les ajustements aux littéraux maison permettrait de mettre du texte entre les guillemets dans operator"" ce qu'il faudra corriger (mais c'est trivial à arranger).
(je dois quitter pour LEWG, et pour aller porter mon plus jeune, Ludo, à son cours de karaté)
À mon arrivée à LEWG, on discute de sparse_array et de structures de données qui utilisent le disque plutôt que la mémoire vive. Je révise ma présentation pendant la pause qui précède ma prestation.
(je ne peux pas prendre de notes pendant que je présente p2596)
RETOUR SUR LA PRÉSENTATION
En soirée (22 h 30 heure de Montréal), la séance de SG6 débute. On a cinq propositions sur la table
C'est une proposition R1, la R0 proposait des trucs comme la moyenne et l'écart type. Celle-ci ajoute des trucs comme le mode. On discute de qualités d'algorithmes, de préconditions, etc. Le nommage est un enjeu (certaines fonctions exigent des intrants triés, et les noms cherchent à refléter cette exigence), et les signatures de fonctions sont compliquées dans l'abstrait (mais simples à utiliser). Les algorithmes sont optimisés en fonction des types d'itérateurs, et sont paramétrés par des types d'accumulateurs. Certains soulignent que les nouveaux types arithmétiques pourront impacter cette proposition
Le discours est au conditionnel, cependant. Àprès vérification, le présentateur confirme qu'il cherche une rétroaction préliminaire de haut niveau quant aux directions poursuivies. Après une heure de présentation (!), on discute sur la direction générale à prendre (en évitant les – nombreuses! – considérations de qualité d'interface, SG6 n'étant pas la bonne audience pour cela).
On parle d'un nouveau générateur de nombres pseudoaléatoires... mais je dois arrêter (trop fatigué)
Manqué, même si ça m'intéressait (trop fatigué)
Manqué, même si ça m'intéressait (trop fatigué)
Manqué, même si ça m'intéressait (trop fatigué)
Une question de santé avec un des enfants m'a fait arriver tout juste avant que CWG ne débute ses travaux. Je pensais être en retard (je suis manifestement très mauvais pour calculer les délais de décalage horaire; je me trompe sans arrêt). J'ai manqué quelques trucs importants durant mon passage à LEWG hier, en particulier les constexpr Structured Bindings (92686) mais je comprends en lisant le Wiki de Core que l'auteur reviendra plus tard cette semaine.
On a quelques irritants techniques avec le son (on entend bien les gens dans la salle mais ils ne nous entendent pas). On finit par régler le problème (la télévision là-bas était sur « mute » même si on pouvait changer le niveau du volume!)
On revient sur les NB Comments résiduels. GB-031 ne progressera pas (l'auteur prospectif n'aura pas le temps cette semaine).
Davis Herring propose une correction aux règles pour les modules important une partition dans laquelle se trouve une constante. Les const sont soumises aux règles du Internal Linkage habituellement et ne se retrouvent pas exportées... ce qui est évidemment problématique! Imaginez : on importe une fonction template qui utilise une constante; la fonction est bien formée dans le module d'origine, mais ne trouve plus la constante lorsqu'elle est instanciée...
La proposition est bonne pour l'essentiel, et nous reviendra après quelques retouches
Jason Merill propose une formulation. L'approche choisie est de faire en sorte que :
« In each such definition, corresponding manifestly-constant-evaluated expressions that are not value-dependent shall have the same value ([expr.const], [temp.dep.constexpr]). »
... ce qui est une direction raisonnable. Joshua Berne pense avoir un exemple de code qui serait brisé par cette résolution (voir https://godbolt.org/z/9bGP8W7Gb) :
// adapted from https://stackoverflow.com/q/61301746/481267
inline auto & g() {
static int a[
([] {
int i = 0;
auto l1 = [](int &j) { return ++j; };
auto l2 = [](int &j) { return j*=2; };
return l1(i) + l2(i);
}())
];
return a;
}
.... notez que l'IIFE détermine une taille de tableau et est donc dans un contexte constexpr. Notez aussi que l'expression l1(i)+l2(i) ne définit pas de manière portable un ordre d'évaluation de sous-expressions. C'est très laid.
Le terme « manifestly-constant-evaluated » doit s'écrire « manifestly constant-evaluated » en conformité avec le reste du standard (oui, on retourne l'auteur à l'écriture pour un trait d'union; c'est la nature de la bête). On débat ensuite pour s'assurer que « manifestly constant-evaluated » est le bon outil pour résoudre ce dilemme. On semble d'accord sur le fait que la résolution est suffisamment générale pour régler un bogue introduit avec consteval.
Tentatively Ready pour le moment
Ceci semble permis par le texte...
struct A {
struct B {
auto foo() { return 0; }
};
decltype(B().foo()) x;
};
... mais ceci aussi, ce qui est plus agaçant (comment savoir à ce stade si Bar::Baz::Baz() existe, surtout si on place static_assert() plus haut?) :
#include <type_traits>
struct Bar {
struct Baz {
int a = 0;
};
static_assert(std::is_default_constructible_v<Baz>);
};
Davis Herring pense que l'enjeu général est la dépendance entre les membres. Jens Maurer rapporte que CWG souhaite retarder l'instanciation des classes pour favoriser le support de ces cas d'utilisation. Entre autres, sans cet ajustement, on ne sait pas comment traiter ceci :
template <class ...> struct partition_indices {
static auto compute_right () {}
static constexpr auto right = compute_right;
};
auto foo () -> partition_indices<>;
void f() {
foo();
};
... ou cela :
template <int> struct X {};
template <class T> struct partition_indices {
static auto compute_right () { return X<I>(); }
static constexpr auto right = compute_right;
static constexpr int I = sizeof(T);
};
auto foo () -> partition_indices<int>;
void f() {
foo();
};
... et on ne veut pas briser ceci :
template <class T> struct A {
static constexpr int num() { return 42; }
int ar[num()];
};
A<int> a;
On revisite cette question à nouveau avec une nouvelle proposition terminologique. Il se peut qu'elle ratisse encore un peu trop large. Ce qu'on veut établir ici est une élision d'accolades comme on le fait régulièrement dans les agrégats, mais sans transformer la règle en trou noir. Une des considérations est de savoir si les mots choisis couvrent correctement les tableaux multidimensionnels.
Ceci ne fonctionne pas, car la conversion implicite de ui en i est une Narrowing Conversion ce qui brise les règles de operator<=>() :
void f(unsigned char i, unsigned ui) {
i <=> ui;
}
Roger Orr propose un ajustement aux règles. On en discute un peu mais ce n'est pas prêt pour adoption encore
(brève pause de 15 minutes; je reviens et on discute des annotations dans le contexte de Core Issue 2538 – Can standard attributes be syntactically ignored?)
Davis Herring explique que EWG souhaite en quelque sorte que nous, CWG, tranchions sur le caractère ignorable ou pas des annotations. On en rediscutera cet après-midi (on a un plus grand nombre de participants ce matin que ce qu'on pense avoir cet après-midi, alors que les discussions sur les contrats draineront une partie de l'équipe, donc on compte essayer de régler les urgences de C++ 23 en priorité)
On a divergence d'implémentation avec un truc comme :
template <class S1, class S2> struct C {
C(...);
};
template<class T1> C(T1) -> C<T1, T1>;
template<class T1, class T2> C(T1, T2) -> C<T1 *, T2>;
template<class V1, class V2> using A = C<V1, V2>;
C c1 { "" };
A a1 { "" };
C c2 { "", 1 };
A a2 { "", 1 };
... car les règles ne disent pas clairement si l'alias A possède les règles de déduction de C. On réfléchit aux diverses pistes qui nous sont offertes
Après la suggestion de EWG de réserver tous les identifiants contenant __ peu importe où, on réexamine la question. Jens Maurer nous informe que Cfront utilise __ dans sa génération de code, ce qui explique cette suggestion!
Jens Maurer a clarifié cette Core Issue pour faciliter les discussions, en particulier pour ce qui touche à la compatibilité entre C et C++.
Jens Maurer a clarifié cette Core Issue pour faciliter les discussions, en particulier pour ce qui touche à la compatibilité entre C et C++.
On est informés que EWG souhaite traiter static_assert() de manière semblable. L'enjeu est qu'un programme qui n'est pas IFNDR et ne compile pas doit offrir au moins un message d'erreur, mais la relation de cette règle avec #error et #warning n'est pas claire (doit-on générer au moins un message? Un message par occurence?). Que faire avec #warning ou #error dans une fonction générique qui ne serait pas instanciée? On mentionne aussi qu'il y a un risque de IFNDR pendant l'exécution du préprocesseur...
Un des enjeux importants ici est d'écrire un texte qui fonctionnera même si les changements à static_assert() discutés cette semaine sont acceptés (ce qui permettrait à un template non instancié et contenant un static_assert(false) de ne pas briser le programme). Au passage, Hubert Tong fait clarifier l'idée de « rejeter » un programme ou de « ne pas accepter » un programme (qu'est-ce que ça signifie exactement?), ce qui peut (éventuellement) influencer les actions posées par des compilateurs JIT.
Après une bonne heure de travail, on a (je pense) quelque chose de satisfaisant
(pause de dîner à Issaquah; je promène Pauline le chien et je fais une brève sieste)
L'idée de celle-ci est de rejoindre C qui a fait la même chose récemment. L'auteur signale que par le passé, il existait des ensembles de caractères qui n'incluaient pas ces caractères, mais ce n'est plus le cas aujourd'hui ce qui rend pertinent le fait de les rendre disponibles (ou du moins, qui enlève un obstacle à leur utilisation).
Ce n'est pas un petit dossier : cela affecte la grammaire, l'analyse lexicale (incluant le préprocesseur), les conversions selon les divers encodages possibles, etc. Certains exemples du passé qui étaient malformés deviennent bien formés, et l'inverse est aussi vrai (certaines dénominations alternatives avec des caractères d'échappement sont interdites quand on réfère à un élément du basic character set)
C a relaxé les règles pour va_start et n'oblige plus une interaction avec le paramètre précédant ... pour naviguer la va_list, alors cette proposition est de rayer plusieurs passages du standard qui ne sont plus nécessaires. Ceci découle de l'avènement de __VA_OPT__ en C++ que C a aussi intégré, et qui permet des macros variadiques vides. Cela affecte principalement <cstdarg>.
On fait remarquer que C++ acceptait void f(...); mais C ne l'acceptait pas (en C++, c'est un truc qu'on utilise pour des manoeuvres de programmation générique, mais on ne peut pas utiliser les paramètres).
On examine ensuite les conséquences de C++ de ce mécanisme, par exemple s'il est enchevêtré avec des instantiations de templates (entre autres, en C++, les contextes non-évalués peuvent mener à des instanciations de templates). On note aussi que cette proposition, qui demande de se baser sur C23 (ou C24), ne pourra pas être adoptée cette semaine pour une raison de chronologie, mais ce n'est pas dramatique (ça vise C++ 26). Hubert Tong signale que certains passages ne sont pas implémentables pour le moment; l'auteur s'assurera que C ajuste les mots pour faciliter la transposabilité aux deux langages.
On le reverra en temps et lieu (peut-être pas cette semaine dû à l'aller-retour entre WG14 et WG21)
C'est une proposition R10 alors elle a beaucoup de maturité, et C a accepté l'équivalent. On épluche la grammaire (c'est intéressant, car c'est un mécanisme un peu novateur du préprocesseur). On a __has_embed(fichier) qui prend une valeur parmi 0, 1 et 2 alors je fais remarquer que ce n'est pas un prédicat malgré son nom, mais Jens Maurer signale que 1 et 2 sont tous deux vrais (l'un est « existe et est non-vide », l'autre est « existe mais vide ». C'est donc un prédicat de C!
(je m'absente 15 minutes pour faire le souper des enfants; je reviens et on est encore sur la grammaire)
Jens Maurer suggère d'épurer certaines répétitions de définitions entre la grammaire et l'explication de certaines facettes sémantiques qui y sont associées. On examine les cas où la ressource à consommer n'a pas une taille de byte qui correspond à CHAR_BIT. On remarque une différence entre les deux langages dans la manière d'exprimer le caractère signé ou non de char (les règles normatives ne sont pas les mêmes).
Il semble y avoir une ambiguïté sur l'utilisation du terme « implementation-defined embed parameter » qui peut être interprété comme une option ou comme une obligation. L'aspect du endianness est discuté (ça peut faire une différence sur une machine où CHAR_BIT==16 par exemple, mais quel est le sens du endianness de #embed alors?). On a un exemple montrant ce qu'une implémentation conforme devrait faire; je propose une manière alternative, et ça semble bien reçu.
(pause de 15 minutes; on travaille fort, ça fait près de deux heures et on n'a pas fini tout à fait)
L'interaction entre #embed et les expansions des macros est ensuite examinée (c'est truffé de pièges!). On a travaillé à peu près trois heures sur celui-là, qui va clairement nous revenir.
Les mécanismes d'allocation pour les coroutines en C++ 20 ne tiennent pas compte d'enjeux de suralignement. Ce que la proposition apporte est une algorithme de décision pour allouer à l'aide du mécanisme le plus approprié. Le texte demande beaucoup de travail (il répète des sections existantes du standard en partie, et semble tenter de de pallier des bogues d'implémentation alors que le Core Text est un enjeu de spécification). On passe beaucoup de temps sur ce texte (90 minutes environ) mais ce n'est pas prêt pour Core. Peut-être plus tard cette semaine...
Les correctifs demandés ont été faits. Tentatively Ready.
Les correctifs demandés ont été faits. Tentatively Ready.
Les correctifs demandés ont été faits. Tentatively Ready.
(assez pour aujourd'hui; je ne ferai pas la séance de soirée pour des raisons familiales)
On commence la journée avec le toujours pertinent et sympathique Gor Nishanov.
L'enjeu est la sous-spécification du moment où promise_type.get_return_object() est effectivement appelé. On nous présente dans le détail les réponses aux questions soulevées par cette Core Issue, c'est très pédagogique. Ce n'est pas un enjeu qui doit être réglé cette semaine (ça ne découle pas d'un NB Comment), mais on veut clarifier l'intention et réduire les risques de divergences entre implémentations. Pour en arriver à un exemple qui exige la situation problématique (on crée le return_object, on fait des trucs ensuite, on le retourne, et quelque chose peut arriver entre-temps), ça prend des situations non-triviales (mais réelles). On passe une bonne trentaine de minutes à éclaircir le propos et Jason Merill finit par décider de se risquer à l'écriture d'une résolution.
Celui-là est vraiment très intéressant car il simplifie la manière d'utiliser des tuple et des packs. C'est du gros travail cependant. Au passage, je constate que dans :
auto foo() -> int(&)[2];
auto [...a] = foo(); // a is a structured binding pack containing 2 elements
auto [b, c, ...d] = foo(); // d is a structured binding pack containing 0 elements
auto [e, f, g, ...h] = foo(); // error: too many identifiers
... un truc comme [b,c,...d] est un identifiant alors que b, c et ...d sont des Structured Bindings. Il y a un enjeu dans le message d'erreur qui entraîne une confusion, ce que je fais corriger.
On expérimente un peu. Par exemple, ceci :
auto [...elems] = Emp{};
template <auto> struct VSink {};
struct A {
A() : VSink<elems>()... {}
};
... semble légal : https://godbolt.org/z/1n96c44dz
Ceci semble demander réflexion (il n'y a rien de dépendant) :
struct C {
int i;
long l;
};
template<typename T>
struct D {
template<int J>
static int f(int);
};
template<>
struct D<long> {
static int f;
};
auto [ ... v ] = C{ 1, 0 };
auto x = ( ... + (D<decltype(v)>::f<1>(2)) );
... et quelques ajouts terminologiques (peut-être l'idée de dependent-expression). C'est le plaisir de CWG : ces exemples sont écrits « live »! Jens Maurer fait remarquer que sizeof... de cette nouvelle bestiole est dependent (probablement value-dependent), mais on vérifie et ce l'était déjà. Ce qui est nouveau est que ça peut être non-dependant si on l'applique sur un Structured Binding Pack qui n'est pas value-dependent. Trouver les ramifications de ces changements dans une spécification d'approximativement 2000 pages est... délicat. Il faut entre autres changer quelques occurrences de instantiated pour expanded du fait que les Packs variadiques peuvent maintenant intervenir dans un contexte non-générique.
On travaille sur le raffinement de quelques exemples, puis on prend une pause de 15 minutes. Je reviens et le groupe a commencé (je promenais Pauline le chien)
Des exemples s'ajoutent, incluant :
struct C
{
int i;
long l;
};
void f(int, long); // #1
auto [ ... v ] = C{ 1, 0 };
void f(decltype(v)... x) // #2 redeclares #1
{ }
... qui cause un crash de compilateur (notez les types des membres de C qui sont distincts), voir https://godbolt.org/z/9PxYd3nKn, et :
struct Point3 { int x, y, z; };
auto [...elems] = Point3{};
void h(int (* ...)[sizeof(elems)]);
void g(int (*q)[4]) {
h(q, q, q);
}
... qui ne cause pas de crash mais est similaire : https://godbolt.org/z/oojebsshs. Notez que l'équivalent générique compile : https://godbolt.org/z/3vPo95so6
struct Point3 { int x, y, z; };
auto [...elems] = Point3{};
template <typename T>
void f(T (* ...)[sizeof(elems)]);
void g(int (*q)[4]) {
f(q, q, q);
}
On fait de gros efforts pour ne pas enchevêtrer ces nouvelles idées avec le code de templates, sinon ça va exploser partout dans le texte du standard. On en vient à un stade où on constate avoir introduit des fonctions variadiques dans un contexte non-générique, une surprise! Je me permet de signaler que si on permet ceci :
struct C { int i, j; };
auto [...v] = C { 2, 3 };
void f(decltype(v)... p) { }
... les gens vont se demander pourquoi on ne permet pas simplement :
void f(int ... p) {}
... qui en découle logiquement. Je pense qu'on ne pourra pas l'éviter, mais il faudra une proposition distincte.
Hubert Tong ajoute un exemple où TU1 et TU2 dénotent deux unités de traduction distinctes, et où il importe que les signatures de f() soient distinctes pour éviter une violation d'ODR :
#if TU1
struct P2 { int x, y; };
namespace {
auto [...elems] = P2{};
}
template <typename T> void f(int (* ...x)[sizeof(T() + elems)]) {}
void g() { f<int>(0, 0); }
#else
struct P3 { int x, y, z; };
namespace {
auto [...elems] = P3{};
}
template <typename T> void f(int (* ...x)[sizeof(T() + elems)]) {}
void h() { f<int>(0, 0, 0); }
#endif
Hubert Tong suggère qu'on doive peut-être expliciter que les elems ici aient un internal linkage. On ajoute la feature test macro manquante et... voilà, presque deux heures de travail profond.
Une nouvelle version de la résolution nous est proposée. Il reste encore des cas à couvrir, par exemple celui où deux types non signés mais de même rang sont comparés (lequel choisir?), ce qui arrive car ce qui suit est possible :
static_assert(!std::is_same_v<unsigned long long, uint64_t>);
static_assert(sizeof(unsigned long long) == sizeof(uint64_t));
Pour le reste par contre, cette résolution résout le problème qu'elle cherche à régler. On harmonisera éventuellement les formulations de cette nouvelle résolution et celles associées aux conversions arithmétiques habituelles. On découvre à la fin que le problème du rang ne surviendra pas avec l'algorithme de décision. On suggère de vérifier que C fait la même chose que nous pour éviter de pervers écarts de comportement... (certains semblent penser que les valeurs pourraient différer à la fin du processus). (note : après vérification, C et C++ semblent conformes dans la procédure, sinon dans les mots)
(pause de dîner à Issaquah; je manque les premières 35 minutes au retour dû à une urgence familiale)
Avant mon retour, CWG a eu le temps de regarder P2788 – Linkage for modular constants et regarde maintenant P2664 – Deduction failure in CTAD for alias templates (ces dossiers demandent l'apport de Davis Herring qui est arrivé entre-temps); pour ce dernier, qui répond à un NB Comment, la solution sur la table est... déplaisante. Le travail se poursuit... On s'entend qu'il faudra faire au moins deux passes dans la résolution alors il faut un premier jet d'ici demain.
Une résolution a été proposée pour cet enjeu. La procédure fut inhabituelle, le changement était estimé « éditorial » par EWG, mais il y a du Core Wording et l'auteur de la résolution a besoin d'un regard... informé. Le texte initial est pas mal (j'ai conservé la notation TEX) :
\begin{note}
The \grammarterm{attribute}s specified in \ref{dcl.attr} have optional semantics: given a well-formed program, removing all instances of a particular such \grammarterm{attribute} results in a program whose observable behavior is a conforming realization of the original program.
\end{note}
... on travaille un peu la formule (p.ex. : un programme n'a pas de comportement observable, mais son exécution en a un), on ajoute des références à la machine abstraite de C++ en essayant de conserver une conformité entre les sections, mais l'essence demeure. C'est extrêmement subtil de définir deux exécutions équivalentes d'un même programme étant donné des intrants équivalents, cela dit (on se bat avec ça pour plus d'une heure; c'est facile à exprimer informellement, mais avec rigueur, c'est complètement différent). La procédure est inhabituelle dans ce cas, alors je suis curieux de voir comment le vote se fera samedi matin.
Le cas de [[no_unique_address]] revient à quelques reprises (d'abord par moi, puis par d'autres), mais les mots choisis à la fin couvrent même cet étrange cas (après tout, cette annotation ne promet pas de rendre les objets plus petits!)
Le texte a été retravaillé un peu, mais il reste du travail à faire pour bien porter l'intention. Hubert Tong remarque que ce texte implique un changement sémantique dans la manière dont les fichiers sources sont traités par un compilateur, mais il y a des désaccords sur sa lecture de la situation. On doit réexaminer ce que signifie le terme preprocessing translation unit et voir les conséquences de cette relecture sur le texte. Ça resserre le texte (on définit formellement des termes qui n'étaient qu'informels par le passé), mais on entre dans un conflit avec la définition des modules (la compilation de C++ se fait en sept phases, et on introduit actuellement une sorte de « phase »)
Tentatively Ready
(pause folle de 17 minutes, assez pour re-promener Pauline le chien!)
On examine la demande de EWG, adoptée par LEWG, soit réserver __ dans un identifiant pour les implémentations. Je me permets ce commentaire :
« It's just a hunch, but I'm under the impression that removing the right to use a space after operator"" will be seen as imposing a space between > and > in templates was in the past. I understand the problem, but that one's going to come back to us... »
... ce à quoi on convient que ce boomerang va revenir à EWG avant de revenir à CWG, ce qui n'est pas faux. Anyway, on n'y peut rien, c'est pas notre responsabilité de juger de ce choix en effet.
Tentatively Ready
La nouvelle version de la résolution évite les pièges d'exiger des décisions prises sur la base d'informations qui peuvent ne pas être connues au moment approprié. On la retravaille encore un peu...
Tentatively Ready
On nous informe que EWG en est arrivé à une décision, mais il nous manque des joueurs pour en discuter. On y reviendra.
Ceci semble résolu par Core Issue 2158.
Ceci fut discuté dans une téléconférence mais aura besoin de réexamen ici (il nous manque un joueur pour ce faire)
La résolution proposée semble convenir. Je note que le terme « signature » exclut le nom et les valeurs des paramètres par défaut (même pour un template).
On pense que NAD serait approprié. Il y a divergence entre les implémentations, mais le texte semble convenable. Il semble que la confusion dans ce dossier remonte à C++ 14 avec un texte malheureux. Hubert Tong signale que C définit un ordre entre deux void* (C++ ne fait pas cela, heureusement selon moi), mais il se peut qu'on s'aligne sur C malgré tout pour réduire les incompatibilités entre les deux langages. Ce sera fait à travers un autre Core Issue, et ça passera par EWG.
La question posée est à savoir si ceci :
template<class T>struct Y {
typedef typename T::value_type blah; // #1
void swap(Y<T>&);
};
template<class T>
void swap(Y<T>& Left, Y<T>& Right) noexcept(noexcept(Left.swap(Right))) { }
template <class T> struct Z {
void swap(Z<T>&);
};
template<class T>
void swap(Z<T>& Left, Z<T>& Right) noexcept(noexcept(Left.swap(Right))) { }
Z<int> x00, y00;
constexpr bool b00 = noexcept(x00.swap(y00));
template void swap(Z<int>&, Z<int>&) noexcept(b00); // #2
... avec instanciation explicite de ceci :
swap<int>(Z<int>&, Z<int>&)
... instancie au point #2 ceci :
swap<int>(Y<int>&, Y<int>&)
... menant à une erreur. On n'a pas de solution pour le moment.
On remarque certains problèmes avec ce texte en lien avec les changements apportés par P1061 ce matin, alors on ajuste.
Nos mots à propos du début de la vie des objets ou de l'espace attribué pour des objets sont ambigus. Il faut resserrer le discours. On retire char de la liste de trucs qui créent des objets plutôt que de l'espace pour des objets.
Tentatively Ready, mais ça risque de nous revenir.
Exposer des constructeurs par using quand la classe ancête visée est virtuellement héritée est... mal. La résolution est un bris d'ABI dans certains cas. L'exemple est ... périlleux. C'est pas des actions recommandables, mais espérons des avertissements à la compilation
EWG a décidé d'ajourner avant de discuter de ceci...
On travaille sur le linkage de noms introduits dans certains types de fichiers, en particulier les modules. C'est mêlant parce que les mêmes sections ont été travaillées par d'autres propositions cette semaine. On remarque qu'il y a plus de noms qui obtiennent external linkage que ce que nous souhaiterions.
(on suspend pour la journée, il se fait tard; je ne sais pas si je vais revenir pour la séance de soirée, ça va dépendre de mon éveil... beaucoup de travail aujourd'hui!)
On commence la journée de bonne humeur. J'ai jonglé plusieurs dossiers pour le travail plus tôt ce matin (ça pleut!)
L'enjeu des suffixes de littéraux maison revient (Core Issue 2521). Il semble que certaines manipulations de macros puissent causer d'autres incidents... Hé la la. Ça va revenir aujourd'hui. Il se peut qu'on doive être plus stricts encore.
On revient sur la question du linkage et des modules, et sur l'impact d'avoir des fonctions static définies dans un .h (c'est pas l'idée du siècle en C++). Évidemment, nous ne sommes pas là pour juger des pratiques des gens, malsaines ou pas.
C'est le dossier chaud du matin, qui recoupe EWG et CWG car cette discussion est en partie évolutionnaire. On passe en revue le texte (dense) en essayant d'éviter les conflits avec C. Le terme « client » est défini en terme de modules; un module peut être client d'une déclaration. Le texte essaie de définir quand deux choses sont une seule et même chose. Davis Herring prend la peine d'expliquer la théorie et la motivation derrière les aspects (mutliples et subtils) de la proposition.
Après une heure de travail intense, Davis Herring se retire pour préparer des exemples (la proposition est hyper dense et exempte d'exemples!). On signale qu'on informera EWG des ajustements pour qu'ils puissent donner leur avis cet après-midi.
Jason Merill explique que ceci règlerait deux Core Issues (Core Issue 2692 et Core Issue 2687) et un enjeu évolutionnaire. On l'examinera en après-midi, plus en détail, mais on examine un passage en particulier ce matin. Ça semble unifier les cas suivants (const ou pas) :
struct A {
static void f(A); // #A
void f(this A); // #B
static void e(A const&); // #C
void e() const&; // #D
void g() {
// static + explicit memfn
(&A::f)(A()); // #1
f(A()); // #2
(&A::f)(); // #3
// static + implicit memfn
(&A::e)(A()); // #4
e(A()); // #5
(&A::e)(); // #6
}
};
Jason Merill dit que EWG a décidé qu'on pourrait « moralement » réaliser l'Overload Resolution sur le résultat de la décrépitude de pointeur. Présentement, #1 est ambigu. La ligne #4 n'est pas ambiguë actuellement mais le deviendrait. C'est amusant comme enjeu : il y a un bogue dans certains compilateurs, on cherchait à le régler, Davis Herring a suggéré une piste différente à brûle-pourpoint, et... EWG a sauté dessus, anticipant en quelque sorte l'intégration de std::invoke() dans le langage plutôt que dans la bibliothèque!
Ça implique plusieurs conséquences surprenantes (le pointeur résultant aurait un sens différent selon l'endroit où il serait synthétisé)... et on se retrouve avec de nouvelles manières d'appeler une fonction!
Il y a dissension (cordiale) ici; c'est la faute de Davis Herring , vraiment, mais Jason Merill n'est pas d'accord (ils demeurent amis!)
(brêve pause de 15 minutes)
Je reviens avec cinq minutes de retard (gérer Pauline le chien, c'est pas toujours simple). On revient sur Core Issue 2687 (et on blague sur le fait que CWG est une sorte de Hive Mind, qui fonctionne par consensus). Après une demi heure de travail, Davis Herring signale que Deducing this, de C++ 23, a apporté des changements considérables sur le langage, et qu'il faudra s'ajuster pendant des années encore, quand bien même ce ne serait que dans le Core Language (mais on s'en doutait).
Hubert Tong fait remarquer que les parenthèses sont à considérer (ils changent le sens de « prendre l'adresse d'un membre »), mais Davis Herring signale que dans les cas que nous spécifions, le contexte est une fonction static (méthode de classe) alors ce n'est pas un enjeu.
Après une heure à retravailler l'algorithme, Jason Merill fait remarquer que ça ressemble à la manière dont nous trions habituellement nos fonctions dans un fichier pour obtenir un graphe acyclique dirigé de manière à faire l'économie de déclarations a priori.
C'est beaucoup de travail; Jens Maurer demande s'il s'agit d'un Core Issue, mais Jason Merill rappelle que certains mécanismes clés de C++ 23 ne fonctionneront pas sans ce correctif.
(pause dîner d'une heure; ils prennent la photo de C++ 23 là-bas alors je ne serai pas sur la photo cette fois encore)
Jens Maurer annonce que les enjeux qui ont occupé notre matin sont désormais discutés chez EWG.
On passe les Tentatively Ready Issues de cette semaine, mais rapidement (il y en a 24, alors avec cinq minutes chacune, ça fait deux heures)
(je ne note pas tout)
On en met quelques-unes de côté (parce que moins pressants, et susceptibles de jouer des tours), incluant Core Issue 1973, 2485, 2531 (qui demande qu'on l'implémente et qu'on l'essaie au préalable, par crainte de briser des éléments de C++ 17, alors ce sera un DR et on le passe de Tentatively Ready à Under Review), 2663,
Dans le cas de l'espace désormais interdit entre "" et _ pour les littéraux maison, le souhait de procéder à une dépréciation est un facteur à considérer (on essaie de donner du temps aux usagers pour s'ajuster) alors on va le proposer, mais séparément pour mettre en relief qu'il est particulier.
Dans le cas des comparaisons relationnelles entre void*, on garde la note non-normative mais on va investiguer ce qui se passe avec C.
Il y a des débats quant au caractère bénin ou pas de Core Issue 2528, mais on le proposera tout de même
La 2678 (source_location::current() est impossible à implémenter) sera aussi proposée séparément étant donné qu'il peut y avoir de la résistance. Cette chose étend la règle ODR.
On revient ensuite au dossier (considérable!) de ce matin sur les Overload Resolution modifiées depuis l'avènement de Deducing this (D2797). On examine l'option d'en extraire une petite partie qui deviendrait un Core Issue, pour garder le coeur du changement en vue d'une proposition pour vote demain. On réexamine le problème original (Core Issue 2687) qui proposait :
struct A {
void f(this A);
static void f(A);
void g();
};
void A::g() {
(&A::f)(A())); // #1
(&A::f)(); // #2
}
... et disait que bien qu'il soit clair que #2 soit incorrect, #1 semble moins évident. On y va par vote (chose rare chez CWG) : maintenant ou plus tard? Et la majorité va pour maintenant. On continue à le regarder; le texte est tel que (&C::c)() et (&(C::c))() ne sont pas soumis aux mêmes règles (le second est l'adresse d'un Overload Set)... Ouf. Je vérifie si les règles sont les mêmes dans la mesure où le code de l'exemple maudit que nous regardons se trouve logé dans une IIFE (j'ai peur!), mais il semble que ce soit le cas (au moins!)
Après beaucoup de travail sur D2797, les exemples qui sont légaux et ceux qui ne sont pas légaux ne sont plus tout à fait les mêmes. L'exemple final est par contre bien meilleur :
class C {
void c(this const &C); // #1
void c() &; // #2
static void c(int i = 0); // #3
void d() {
c(); // ambiguous between #2 and #3
(C::c)(); // error, as above
(&C::c)(C{}); // selects #1
(&C::c)(*this); // selects #2 and is ill-formed
(&(C::c))(); // error : address of an overload set
(&C::c)(); // selects #3
}
};
(je dois m'absenter pour une heure – incluant la pause – pour raisons familiales; à mon retour, on discute de P2593 qui permettra d'utiliser static_assert(false) dans une branche non-générée d'un if constexpr). L'idée est de faire (enfin!) compiler ceci :
template <class T>
void f(T arg) {
if constexpr(sizeof(T) == sizeof(int))
use(arg);
else
static_assert(false, "must be int-sized");
}
// ...
void g(char c) {
f(0); // Ok
f(c); // error, must be int-sized
}
... même si le false ne dépend pas de T. Il y a des liens avec #error et #warning alors on se demande si on les passe en bloc, séparément, si on attend... On vote et on dit unanimement « oui, on fonce » alors il se peut que ça bloque demain. Notez qu'il y a une différence entre ceci :
template <class T> void f(T) {
if constexpr(sizeof(T) == 2) {
static_assert(false);
}
}
... qui compile, et cela :
template <class T> void f(T) {
if constexpr(sizeof(T) == 2) {
#error "oops"
}
}
... qui ne compile pas, car ce qui commence par un # est traité par le préprocesseur (bien avant que le compilateur n'ait accès au texte).
On convient que tout cela est un DR... pour C++ 20.
Ça parle des NTTP de C++ 20. En gros, c'est un micro ajustement à la grammaire pour permettre d'utiliser {} pour les valeurs par défaut de paramètres de templates si c'est une expression constexpr. L'algorithme est subtil car il doit tenir compte de l'importance des adresses dans ces paramètres, ce qui importe par exemple si on doit comparer deux pointeurs dans un même tableau
(j'ai dû quitter à 20 minutes de la fin)
C'est la fin de cette grosse semaine, et nous serons en plénière à 8 h 30 heure d'Issaquah (11 h 30 heure de Montréal) pour voter sur les résolutions de CWG, LWG (dont un un peu différent des autres car il portera sur la Concurrency TS v2) et sur deux résolutions de WG21. Je passe quelques heures à préparer ce qui suit pour noter les décisions qui seront prises.
Je me connecte une quinzaine de minutes d'avance et il y a déjà quelques participant(e)s. John Spicer, Chair de WG21, prépare la présentation de ce matin. On apprend qu'on prendra une photo des participants Zoom tantôt alors je serai là si la technique tient la route!
On fait le tour des rapports des groupes d'étude :
On discute brièvement de trucs administratifs, par exemple signaler notre présence si ce ne fut fait
On poursuit avec les groupes de travail :
Les fruits de cette semaine de travail suivent.
Adopté à l'unanimité
Adopté à l'unanimité
Ville Voutilainen signale qu'il lui semble déplaisant que l'on intègre des Breaking Changes a posteriori. Vote demandé : consensus
Vote demandé (sans surprises, c'est un peu controversé) : consensus, mais avec plusieurs abstentions
Adopté à l'unanimité
Adopté à l'unanimité
On demande si CWG a considéré faire du feature sur lequel cela dépend un DR, pour que ce mécanisme puisse être intégré à C++ 20 a posteriori. Jens Maurer dit que ça a été discuté mais que ça n'a pas passé. Herb Sutter rappelle que si on avait fait cela, il aurait fallu passer par EWG.
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Grosse semaine où tout le matériel pour C++ 23 a été traité. Des efforts investis sur Senders / Receivers et sur C++ 26 aussi. Outre la TS, la plupart des trucs sur la table aujourd'hui sont des Bug Fixes.
Le premier vote porte sur la Concurrency TS v2. Les autres portent sur C++ 23.
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Vote demandé : consensus (mais beaucoup d'absentions)
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Adopté à l'unanimité
Vote demandé : consensus (plusieurs abstentions)
Vote demandé : consensus (plusieurs abstentions)
Vote demandé : consensus (plusieurs abstentions)
Adopté à l'unanimité
Adopté à l'unanimité
Vote demandé : consensus (plusieurs abstentions)
Adopté à l'unanimité
Vote demandé : consensus (plusieurs abstentions)
Adopté à l'unanimité
Deux votes touchent WG21 dans son ensemble.
Adopté à l'unanimité
Adopté à l'unanimité
Herb Sutter remercie tous les gens qui ont participé aux travaux à travers la pandémie. On s'applaudit, et on prend la photo par Zoom (ça prend deux écrans!)
On examine certains dossiers administratifs (p. ex. : on nous permet la retranscription automatique, mais est-ce en contravention des permissions de noter ce qui se passe pendant que ça se passe? ISO nous dit de ne pas l'enregistrer pour le moment)
La prochaine rencontre in vivo envisageable est Varna (Bulgarie), organisé entre autres par VMWare en juin 2023 (https://wg21.link/n4935) puis Kona (É.-U.) en novembre 2023.
On souligne (avec raison!) que nous avons réussi encore une fois à respecter le rythme de trois ans pour chaque version de C++ depuis 2011. Quand même...!