J'ai regardé le discours de Walter Brown au Cppcon14 sur la programmation de modèles modernes ( Partie I , Partie II ) où il a présenté sa void_t
technique SFINAE.
Exemple: étant
donné un modèle de variable simple qui évalue void
si tous les arguments du modèle sont bien formés:
template< class ... > using void_t = void;
et le trait suivant qui vérifie l'existence d'une variable membre appelée membre :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
J'ai essayé de comprendre pourquoi et comment cela fonctionne. Par conséquent, un petit exemple:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
existedecltype( A::member )
est bien formévoid_t<>
est valide et évalue àvoid
has_member< A , void >
et donc il choisit le modèle spécialiséhas_member< T , void >
et évalue àtrue_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
n'existe pasdecltype( B::member )
est mal formé et échoue silencieusement (sfinae)has_member< B , expression-sfinae >
donc ce modèle est supprimé
- le compilateur trouve
has_member< B , class = void >
avec void comme argument par défaut has_member< B >
évalue àfalse_type
Questions:
1. Ma compréhension de cela est-elle correcte?
2. Walter Brown déclare que l'argument par défaut doit être exactement du même type que celui utilisé void_t
pour que cela fonctionne. Pourquoi donc? (Je ne vois pas pourquoi ces types doivent correspondre, est-ce que n'importe quel type par défaut fait le travail?)
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
.has_member< T , class = void >
défautvoid
. En supposant que ce trait ne sera utilisé qu'avec 1 argument de modèle à tout moment, alors l'argument par défaut pourrait être n'importe quel type?template <class, class = void>
pourtemplate <class, class = void_t<>>
. Alors maintenant, nous sommes libres de faire ce que nous voulons avec l'void_t
implémentation du modèle d'alias :)Réponses:
1. Modèle de classe primaire
Lorsque vous écrivez
has_member<A>::value
, le compilateur recherche le nomhas_member
et trouve le modèle de classe primaire , c'est-à-dire cette déclaration:(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 écrithas_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: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
etvoid_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èleT
. C'est une déduction triviale, mais considérez un modèle commeT const&
, où nous pourrions encore déduireT
. Pour le modèleT
et l'argument modèleA
, nous en déduisonsT
êtreA
.Dans le deuxième modèle
void_t< decltype( T::member ) >
, le paramètre de modèleT
apparaît dans un contexte où il ne peut être déduit d'aucun argument de modèle.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:
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: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:
On se retrouve avec la même spécialisation:
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:
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.
la source
Cette spécialisation ci-dessus n'existe que lorsqu'elle est bien formée, donc quand elle
decltype( T::member )
est valide et non ambiguë. la spécialisation est ainsi pour l'has_member<T , void>
état dans le commentaire.Lorsque vous écrivez
has_member<A>
, c'est àhas_member<A, void>
cause de l'argument de modèle par défaut.Et nous avons une spécialisation pour
has_member<A, void>
(donc hériter detrue_type
) mais nous n'avons pas de spécialisation pourhas_member<B, void>
(nous utilisons donc la définition par défaut: hériter defalse_type
)la source