J'ai vu quelques exemples de C ++ utilisant des paramètres de modèle de modèle (c'est-à-dire des modèles qui prennent des modèles comme paramètres) pour effectuer une conception de classe basée sur des règles. Quelles sont les autres utilisations de cette technique?
c++
templates
template-templates
Ferruccio
la source
la source
Réponses:
Je pense que vous devez utiliser la syntaxe du modèle de modèle pour passer un paramètre dont le type est un modèle dépendant d'un autre modèle comme celui-ci:
Voici
H
un modèle, mais je voulais que cette fonction traite de toutes les spécialisations deH
.REMARQUE : je programme c ++ depuis de nombreuses années et je n'en ai eu besoin qu'une seule fois. Je trouve que c'est une fonctionnalité rarement nécessaire (bien sûr pratique quand vous en avez besoin!).
J'ai essayé de penser à de bons exemples, et pour être honnête, la plupart du temps ce n'est pas nécessaire, mais inventons un exemple. Faisons semblant que
std::vector
ne pas avoir untypedef value_type
.Alors, comment écririez-vous une fonction qui peut créer des variables du bon type pour les éléments vecteurs? Cela fonctionnerait.
REMARQUE :
std::vector
a deux paramètres de modèle, type et allocateur, nous avons donc dû les accepter tous les deux. Heureusement, en raison de la déduction de type, nous n'aurons pas besoin d'écrire explicitement le type exact.que vous pouvez utiliser comme ceci:
ou mieux encore, nous pouvons simplement utiliser:
MISE À JOUR : Même cet exemple artificiel, bien qu'illustratif, n'est plus un exemple étonnant en raison de l'introduction de c ++ 11
auto
. Maintenant, la même fonction peut s'écrire:c'est ainsi que je préférerais écrire ce type de code.
la source
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
et nonf<vector<int>>
.f<vector,int>
moyensf<ATemplate,AType>
,f<vector<int>>
moyensf<AType>
En fait, l'utilisation des paramètres de modèle de modèle est plutôt évidente. Une fois que vous apprenez que stdlib C ++ a un trou béant de ne pas définir d'opérateurs de sortie de flux pour les types de conteneurs standard, vous devez écrire quelque chose comme:
Ensuite, vous comprendrez que le code pour le vecteur est exactement le même, car forward_list est le même, en fait, même pour une multitude de types de cartes, c'est toujours le même. Ces classes de modèles n'ont rien en commun, à l'exception de la méta-interface / du protocole, et l'utilisation du paramètre de modèle de modèle permet de capturer la similitude dans chacun d'eux. Avant de procéder à l'écriture d'un modèle, il convient de vérifier une référence pour rappeler que les conteneurs de séquence acceptent 2 arguments de modèle - pour le type de valeur et l'allocateur. Bien que l'allocateur soit par défaut, nous devons toujours tenir compte de son existence dans notre opérateur de modèle <<:
Voila, cela fonctionnera automatiquement pour tous les conteneurs de séquence actuels et futurs adhérant au protocole standard. Pour ajouter des cartes au mélange, il faudrait jeter un œil à la référence pour noter qu'elles acceptent 4 paramètres de modèle, nous aurions donc besoin d'une autre version de l'opérateur << ci-dessus avec le paramètre de modèle de modèle à 4 arguments. Nous verrions également que std: pair essaie d'être rendu avec l'opérateur 2-arg << pour les types de séquence que nous avons définis précédemment, donc nous fournirions une spécialisation juste pour std :: pair.
De plus, avec C + 11 qui autorise les modèles variadiques (et devrait donc autoriser les arguments de modèle de modèle variadique), il serait possible d'avoir un seul opérateur << pour les gouverner tous. Par exemple:
Production
la source
__PRETTY_FUNCTION__
, qui, entre autres, rapporte les descriptions des paramètres du modèle en texte brut. clang le fait aussi. Une fonctionnalité très pratique parfois (comme vous pouvez le voir).Voici un exemple simple tiré de 'Modern C ++ Design - Generic Programming and Design Patterns Applied' par Andrei Alexandrescu:
Il utilise une classe avec des paramètres de modèle de modèle afin d'implémenter le modèle de politique:
Il explique: En règle générale, la classe hôte connaît déjà, ou peut facilement en déduire, l'argument modèle de la classe de règles. Dans l'exemple ci-dessus, WidgetManager gère toujours les objets de type Widget, donc demander à l'utilisateur de spécifier à nouveau Widget dans l'instanciation de CreationPolicy est redondant et potentiellement dangereux.Dans ce cas, le code de bibliothèque peut utiliser des paramètres de modèle de modèle pour spécifier des politiques.
L'effet est que le code client peut utiliser 'WidgetManager' d'une manière plus élégante:
Au lieu de la manière la plus lourde et la plus sujette aux erreurs qu'une définition manquant d'arguments de modèle de modèle aurait nécessité:
la source
Voici un autre exemple pratique de ma bibliothèque de réseaux de neurones convolutionnels CUDA . J'ai le modèle de classe suivant:
qui implémente en fait la manipulation de matrices à n dimensions. Il existe également un modèle de classe enfant:
qui implémente la même fonctionnalité mais en GPU. Les deux modèles peuvent fonctionner avec tous les types de base, comme float, double, int, etc. Et j'ai également un modèle de classe (simplifié):
La raison ici d'avoir une syntaxe de modèle de modèle est parce que je peux déclarer l'implémentation de la classe
qui aura à la fois des poids et des entrées de type float et sur GPU, mais connection_matrix sera toujours int, soit sur CPU (en spécifiant TT = Tensor) soit sur GPU (en spécifiant TT = TensorGPU).
la source
Supposons que vous utilisez CRTP pour fournir une "interface" pour un ensemble de modèles enfants; et le parent et l'enfant sont paramétriques dans d'autres arguments de modèle:
Notez la duplication de 'int', qui est en fait le même paramètre de type spécifié pour les deux modèles. Vous pouvez utiliser un modèle de modèle pour DERIVED pour éviter cette duplication:
Notez que vous éliminez la fourniture directe des autres paramètres de modèle au modèle dérivé ; "l'interface" les reçoit toujours.
Cela vous permet également de créer des typedefs dans l '"interface" qui dépendent des paramètres de type, qui seront accessibles à partir du modèle dérivé.
Le typedef ci-dessus ne fonctionne pas car vous ne pouvez pas typedef vers un modèle non spécifié. Cela fonctionne cependant (et C ++ 11 a un support natif pour les typedefs de modèle):
Vous avez malheureusement besoin d'un dérivé_interface_type pour chaque instanciation du modèle dérivé, sauf s'il y a une autre astuce que je n'ai pas encore apprise.
la source
derived
peut être utilisée sans ses arguments de modèle, c'est-à-dire la lignetypedef typename interface<derived, VALUE> type;
template <typename>
. Dans un sens, vous pouvez considérer les paramètres du modèle comme ayant un «métatype»; le métatype normal pour un paramètre de modèle esttypename
ce qui signifie qu'il doit être rempli par un type régulier; letemplate
métatype signifie qu'il doit être rempli avec une référence à un modèle.derived
définit un modèle qui accepte untypename
paramètre métatypé, il correspond donc à la facture et peut être référencé ici. Ça a du sens?typedef
. En outre, vous pouvez éviter le doublonint
dans votre premier exemple en utilisant une construction standard telle quevalue_type
dans le type DERIVED.typedef
problème à partir du bloc 2. Mais le point 2 est valide, je pense ... oui, ce serait probablement un moyen plus simple de faire la même chose.Voici ce que j'ai rencontré:
Peut être résolu pour:
ou (code de travail):
la source
Dans la solution avec les modèles variadic fournis par pfalcon, j'ai trouvé difficile de spécialiser réellement l'opérateur ostream pour std :: map en raison de la nature gourmande de la spécialisation variadique. Voici une légère révision qui a fonctionné pour moi:
la source
En voici une généralisée à partir de quelque chose que je viens d'utiliser. Je le poste car c'est un exemple très simple, et il montre un cas d'utilisation pratique avec des arguments par défaut:
la source
Il améliore la lisibilité de votre code, offre une sécurité de type supplémentaire et économise certains efforts du compilateur.
Supposons que vous souhaitiez imprimer chaque élément d'un conteneur, vous pouvez utiliser le code suivant sans paramètre de modèle de modèle
ou avec un paramètre de modèle de modèle
Supposons que vous transmettiez un entier
print_container(3)
. Pour le premier cas, le modèle sera instancié par le compilateur qui se plaindra de l'utilisation dec
dans la boucle for, ce dernier n'instanciera pas du tout le modèle car aucun type correspondant ne peut être trouvé.De manière générale, si votre classe / fonction de modèle est conçue pour gérer la classe de modèle en tant que paramètre de modèle, il est préférable de le préciser.
la source
Je l'utilise pour les types versionnés.
Si vous avez un type versionné via un modèle tel que
MyType<version>
, vous pouvez écrire une fonction dans laquelle vous pouvez capturer le numéro de version:Vous pouvez donc faire différentes choses en fonction de la version du type transmis au lieu d'avoir une surcharge pour chaque type. Vous pouvez également avoir des fonctions de conversion qui acceptent
MyType<Version>
et retournentMyType<Version+1>
, de manière générique, et même les récurrent pour avoir uneToNewest()
fonction qui renvoie la dernière version d'un type de n'importe quelle version plus ancienne (très utile pour les journaux qui auraient pu être stockés il y a quelque temps) mais doivent être traitées avec l'outil le plus récent du jour).la source