J'ai regardé le discours de Walter Brown à la conférence Cppcon14 sur la programmation de modèles modernes ( Partie I , Partie II ) où il a présenté sa technique void_t
SFINAE.
Exemple:
Étant donné un modèle de variable simple qui donne 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 est évalué à void
has_member< A , void >
Et choisit donc le modèle spécialiséhas_member< T , void >
Et est évalué à 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 >
, Ce modèle est donc jetéhas_member< B , class = void >
avec void comme argument par défauthas_member< B >
Est évalué à false_type
Des questions:
1. Est-ce que ma compréhension de ceci est correcte?
2. Walter Brown déclare que l'argument par défaut doit être exactement du même type que celui utilisé dans void_t
Pour que cela fonctionne. Pourquoi donc? (Je ne vois pas pourquoi ces types doivent correspondre, aucun type par défaut ne fait-il le travail?)
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 le PO, c'est écrit comme une définition.)
La liste d'arguments de modèle <A>
Est comparée à la liste de paramètres de modèle de ce modèle principal. Comme le modèle principal a deux paramètres, mais que vous n'en avez fourni qu'un, le paramètre restant est défini par défaut sur l'argument de modèle par défaut: void
. C'est comme si vous aviez écrit has_member<A, void>::value
.
Maintenant, la liste des paramètres de 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. Donc, 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 de modèle A, void
Aux 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 modèle-paramètre T
. Ceci est une déduction triviale, mais considérons un modèle comme T const&
, Où nous pourrions toujours déduire T
. Pour le motif T
et l'argument de modèle A
, nous déduisons que T
est A
.
Dans le deuxième motif 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 de decltype
est explicitement exclue de la déduction d'argument de modèle. Je suppose que cela est dû au fait que cela peut être arbitrairement complexe.
Même si nous utilisions un modèle sans decltype
comme void_t< T >
, Alors la déduction de T
a lieu sur le modèle d'alias résolu. En d’autres termes, nous résolvons le modèle d’alias, puis nous essayons de déduire le type T
du modèle obtenu. Le modèle obtenu est cependant void
, ce 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 d'essayer d'inverser une fonction constante (au sens mathématique de ces termes).
La déduction de l'argument modèle est terminée(*), maintenant les arguments déduits sont substitué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
{ };
Nous pouvons maintenant comparer la liste des 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, cette spécialisation partielle est donc 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
{ };
Nous nous retrouvons avec la même spécialisation:
template<>
struct has_member<A, void> : true_type
{ };
mais notre liste d'arguments de modèle pour has_member<A>::value
est maintenant <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.
(*) Le Standard, à mon humble avis, inclut le processus de substitution et la correspondance des arguments de modèle spécifiés explicitement 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 , il faut en déduire tous les paramètres de gabarit de la spécialisation partielle; cela signifie également que la substitution doit réussir et (comme il semble?) les arguments de modèle doivent correspondre aux paramètres de modèle (substitués) de la spécialisation partielle. Notez que je ne suis pas complètement au courant de où la norme spécifie la comparaison entre la liste d'arguments substitués et la liste d'arguments fournie.
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
La spécialisation ci-dessus n’existe que si elle est bien formée, donc lorsque decltype( T::member )
est valide et non ambigu. la spécialisation est la même pour has_member<T , void>
comme indiqué dans le commentaire.
Lorsque vous écrivez has_member<A>
, Il s'agit de has_member<A, void>
En raison de l'argument de modèle par défaut.
Et nous avons une spécialisation pour has_member<A, void>
(Héritez donc de true_type
) Mais nous n'avons pas de spécialisation pour has_member<B, void>
(Nous utilisons donc la définition par défaut: hériter de false_type
)