Donné :
#include <concepts>
#include <iostream>
template<class T>
struct wrapper;
template<std::signed_integral T>
struct wrapper<T>
{
wrapper() = default;
void print()
{
std::cout << "signed_integral" << std::endl;
}
};
template<std::integral T>
struct wrapper<T>
{
wrapper() = default;
void print()
{
std::cout << "integral" << std::endl;
}
};
int main()
{
wrapper<int> w;
w.print(); // Output : signed_integral
return 0;
}
Du code ci-dessus, int
qualifie à la fois std::integral
et std::signed_integral
concept.
Étonnamment, cela compile et imprime "signed_integral" sur les compilateurs GCC et MSVC. Je m'attendais à ce qu'il échoue avec une erreur du type "la spécialisation du modèle a déjà été définie".
D'accord, c'est légal, c'est juste, mais pourquoi a été std::signed_integral
choisi à la place de std::integral
? Y a-t-il des règles définies dans la norme avec quelle spécialisation de modèle est choisie lorsque plusieurs concepts se qualifient pour l'argument de modèle?
c++
language-lawyer
c++20
c++-concepts
Lewis Liman
la source
la source
Réponses:
En effet, les concepts peuvent être plus spécialisés que d'autres, un peu comme la façon dont les modèles s'ordonnent. C'est ce qu'on appelle l'ordre partiel des contraintes
Dans le cas des concepts, ils se subsument mutuellement lorsqu'ils incluent des contraintes équivalentes. Par exemple, voici comment
std::integral
etstd::signed_integral
sont mis en œuvre:En normalisant les contraintes, le compilateur résume l'expression de contraint à ceci:
Dans cet exemple,
signed_integral
impliqueintegral
complètement. Donc, dans un sens, une intégrale signée est "plus contrainte" qu'une intégrale.La norme l'écrit comme ceci:
À partir de [temp.func.order] / 2 (c'est moi qui souligne):
Cela signifie que s'il existe plusieurs substitutions possibles pour un modèle et que les deux sont choisis dans un ordre partiel, il sélectionnera le modèle le plus contraint.
Depuis [temp.constr.order] / 1 :
Ceci décrit l'algorithme de subsomption que le compilateur utilise pour ordonner les contraintes et donc les concepts.
la source
C ++ 20 a un mécanisme pour décider quand une entité contrainte particulière est "plus contrainte" qu'une autre. Ce n'est pas simple.
Cela commence par le concept de décomposer une contrainte en ses composants atomiques, un processus appelé normalisation des contraintes . C'est grand et trop complexe pour être abordé ici, mais l'idée de base est que chaque expression dans une contrainte est décomposée en ses pièces conceptuelles atomiques, récursivement, jusqu'à ce que vous atteigniez une sous-expression de composant qui n'est pas un concept.
Donc, étant donné cela, regardons comment les concepts
integral
et sont définis :signed_integral
La décomposition de
integral
est justeis_integral_v
. La décomposition designed_integral
isis_integral_v && is_signed_v
.Nous arrivons maintenant au concept de subsomption de contraintes . C'est un peu compliqué, mais l'idée de base est qu'une contrainte C1 est censée «subsumer» une contrainte C2 si la décomposition de C1 contient chaque sous-expression dans C2. Nous pouvons voir que
integral
ne subsume passigned_integral
, maissigned_integral
ne subsumerintegral
, car il contient toutintegral
fait.Ensuite, nous arrivons à classer les entités contraintes:
Parce que
signed_integral
subsumeintegral
, le<signed_integral> wrapper
est "au moins aussi contraint" que le<integral> wrapper
. Cependant, l'inverse n'est pas vrai, car la subsomption n'est pas réversible.Par conséquent, conformément à la règle pour les entités "plus contraintes":
Étant donné que le
<integral> wrapper
n'est pas au moins aussi contraint que<signed_integral> wrapper
, ce dernier est considéré comme plus contraint que le premier.Et donc, lorsque les deux pourraient s'appliquer tous les deux, la déclaration la plus contrainte l'emporte.
Sachez que les règles de subsomption de contraintes s'arrêtent lorsqu'une expression est rencontrée qui n'est pas a
concept
. Donc, si vous avez fait ça:Dans ce cas,
my_signed_integral
ne subsume passtd::integral
. Même s'ilmy_is_integral_v
est défini de manière identique àstd::is_integral_v
, car il ne s'agit pas d'un concept, les règles de subsomption de C ++ ne peuvent pas le parcourir pour déterminer qu'elles sont identiques.Les règles de subsomption vous encouragent donc à construire des concepts à partir d'opérations sur des concepts atomiques.
la source
Avec Partial_ordering_of_constraints
et
Et le concept
std::signed_integral
subsume lestd::integral<T>
concept:Votre code est donc correct, tout comme il
std::signed_integral
est plus "spécialisé".la source