1. Modèle de classe primaire
Lorsque vous écrivez has_member<A>::value
, le compilateur recherche le nom has_member
et trouve le modèle de classe primaire , c'est-à-dire cette déclaration:
template< class , class = void >
struct has_member;
(Dans l'OP, c'est écrit comme une définition.)
La liste des arguments de modèle <A>
est comparée à la liste de paramètres de modèle de ce modèle principal. Étant donné que le modèle primaire a deux paramètres, mais vous ne fourni un, le paramètre reste est réglé par défaut à l'argument de modèle par défaut: void
. C'est comme si tu avais écrit has_member<A, void>::value
.
2. Modèle de classe spécialisée
Désormais , la liste des paramètres du modèle est comparée à toutes les spécialisations du modèle has_member
. Seulement si aucune spécialisation ne correspond, la définition du modèle principal est utilisée comme solution de secours. Ainsi, la spécialisation partielle est prise en compte:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Le compilateur essaie de faire correspondre les arguments du modèle A, void
avec les modèles définis dans la spécialisation partielle: T
et void_t<..>
un par un. Tout d'abord , la déduction des arguments de modèle est effectuée. La spécialisation partielle ci-dessus est toujours un modèle avec des paramètres de modèle qui doivent être "remplis" par des arguments.
Le premier modèle T
, permet au compilateur de déduire le paramètre-modèle T
. C'est une déduction triviale, mais considérez un modèle comme T const&
, où nous pourrions encore déduire T
. Pour le modèle T
et l'argument modèle A
, nous en déduisons T
être A
.
Dans le deuxième modèle void_t< decltype( T::member ) >
, le paramètre de modèle T
apparaît dans un contexte où il ne peut être déduit d'aucun argument de modèle.
Il y a deux raisons à cela:
L'expression à l'intérieur decltype
est explicitement exclue de la déduction des arguments de modèle. Je suppose que c'est parce que cela peut être arbitrairement complexe.
Même si nous avons utilisé un modèle sans decltype
like void_t< T >
, alors la déduction de T
se produit sur le modèle d'alias résolu. Autrement dit, nous résolvons le modèle d'alias et essayons plus tard de déduire le type T
du modèle résultant. Le modèle résultant, cependant, est void
, qui ne dépend pas de T
et ne nous permet donc pas de trouver un type spécifique pour T
. Ceci est similaire au problème mathématique consistant à essayer d'inverser une fonction constante (au sens mathématique de ces termes).
La déduction des arguments de modèle est terminée (*) , maintenant les arguments de modèle déduits sont remplacés. Cela crée une spécialisation qui ressemble à ceci:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
Le type void_t< decltype( A::member ) >
peut maintenant être évalué. Il est bien formé après la substitution, par conséquent, aucun échec de substitution ne se produit. On a:
template<>
struct has_member<A, void> : true_type
{ };
3. Choix
Maintenant , nous pouvons comparer la liste de paramètres de modèle de cette spécialisation avec les arguments de modèle fournis à l'original has_member<A>::value
. Les deux types correspondent exactement, donc cette spécialisation partielle est choisie.
D'autre part, lorsque nous définissons le modèle comme:
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
On se retrouve avec la même spécialisation:
template<>
struct has_member<A, void> : true_type
{ };
mais notre liste d'arguments de modèle pour l' has_member<A>::value
instant est <A, int>
. Les arguments ne correspondent pas aux paramètres de la spécialisation et le modèle principal est choisi comme solution de secours.
(*) La norme, à mon humble avis, inclut le processus de substitution et la correspondance des arguments de modèle explicitement spécifiés dans le processus de déduction des arguments de modèle . Par exemple (post-N4296) [temp.class.spec.match] / 2:
Une spécialisation partielle correspond à une liste d'arguments de modèle réelle donnée si les arguments de modèle de la spécialisation partielle peuvent être déduits de la liste d'arguments de modèle réelle.
Mais cela ne signifie pas seulement que tous les paramètres de modèle de la spécialisation partielle doivent être déduits; cela signifie également que la substitution doit réussir et (comme il semble?) les arguments du modèle doivent correspondre aux paramètres de modèle (substitués) de la spécialisation partielle. Notez que je ne sais pas complètement où la norme spécifie la comparaison entre la liste d'arguments substitués et la liste d'arguments fournie.
has_member<A,int>::value
. Ensuite, la spécialisation partielle évaluéehas_member<A,void>
ne peut pas correspondre. Par conséquent, il doit êtrehas_member<A,void>::value
, ou, avec le sucre syntaxique, un argument par défaut de typevoid
.