Le langage C++ continue d’affiner son système de concepts. Alors que les concepts basés sur des expressions de pliage (fold expressions) existent depuis C++20, un angle mort subsistait : le compilateur ne parvenait pas à comparer deux contraintes construites avec des expressions de pliage, même lorsque l’une subsume logiquement l’autre. La révision C++26, via le document technique P2963R3, vient combler cette lacune.
Le problème : des contraintes pourtant ordonnées jugées incomparables
Le mécanisme de subsomption est au cœur du classement des surcharges en C++ moderne. Si un concept C est défini comme la conjonction de deux autres concepts A et B (C = A && B), alors toute expression satisfaisant C satisfait aussi A. Le compilateur est capable de reconnaître cette relation et, en cas d’ambiguïté entre deux surcharges, de choisir la plus contrainte.
Mais cette logique échouait avec les expressions de pliage. Imaginons deux surcharges d’une fonction g() :
- La première exige
A<T>pour chaque élément d’un pack de paramètres :requires (A<T> && ...). - La seconde exige
C<T>, oùCest un concept plus strict subsumantA:requires (C<T> && ...).
Intuitivement, le deuxième appel est plus contraint. Pourtant, en C++20 et C++23, le compilateur refusait de trancher et signalait une ambiguïté. La raison est technique : les expressions (A<T> && ...) et (C<T> && ...) étaient toutes deux traitées comme une unique contrainte atomique lors de la normalisation. Or la subsomption compare les contraintes atomiques par leur emplacement dans le code source, et non par leur contenu logique. L’expression de pliage formait une barrière que le mécanisme ne pouvait pas franchir pour inspecter le motif à l’intérieur.
La solution : les contraintes étendues par pli
C++26 introduit une nouvelle catégorie de contraintes, baptisée fold expanded constraints (contraintes étendues par pli), qui s’ajoute aux conjonctions, disjonctions et contraintes atomiques déjà existantes. Une contrainte étendue par pli capture à la fois le motif à l’intérieur du pli et l’opérateur de pliage (&& ou ||).
Les règles de subsomption sont alors étendues : une contrainte (P<Ts> && ...) subsume (Q<Ts> && ...) si trois conditions sont réunies :
- les deux expressions déplient le même pack de paramètres,
- elles utilisent le même opérateur de pliage,
P<T>subsumeQ<T>pour un type uniqueT.
Avec cette modification, l’exemple problématique devient valide en C++26 : le compilateur sélectionne correctement la seconde surcharge, plus contrainte.
Opérateurs et homogénéité
Une restriction importante est que les opérateurs de pliage doivent être identiques. Une contrainte (P<Ts> && ...) ne subsume pas (P<Ts> || ...), et inversement. Les deux formes expriment des exigences logiquement distinctes : la première requiert P pour tous les éléments, la seconde pour au moins un. Elles sont donc incomparables du point de vue de la subsomption.
Lien avec P2841 et les packs de concepts
P2963R3 est issu des travaux sur P2841, une proposition antérieure introduisant les paramètres template de concepts et de variables. Ce lien est important car P2841 ouvre la voie à des packs de concepts – c’est-à-dire des packs de paramètres de type concept, et non plus uniquement des types. Lorsque le motif d’une expression de pliage contient un pack de paramètres template de concept non déplié (rendu possible par P2841), P2963R3 prévoit un traitement spécial : la contrainte est entièrement décomposée en conjonctions ou disjonctions atomiques en fonction de la taille du pack, plutôt que d’être traitée comme une unique contrainte étendue par pli. Cela garantit que la subsomption fonctionne correctement y compris quand des concepts eux-mêmes sont passés comme arguments template.
Mise en œuvre
Au moment de la rédaction de cet article, le compilateur Clang 19 supporte déjà P2963R3.
Conclusion
Le système de subsomption de contraintes a toujours présenté des cas limites – l’utilisation de parenthèses autour de négations, ou encore des expressions de sens identique mais de localisations source différentes, qui empêchaient la comparaison. Les expressions de pliage ajoutaient un nouvel angle mort : des contraintes conceptuellement ordonnées que le compilateur traitait comme incomparables. P2963R3 corrige cette lacune en intégrant pleinement les contraintes étendues par pli dans le mécanisme de subsomption. Les développeurs peuvent désormais écrire des surcharges variadiques contraintes par expressions de pliage, et le compilateur les ordonne correctement en fonction de leur niveau de contrainte.