J'ai vu quelques exemples de C++ en utilisant des paramètres de modèle de modèle (c'est-à-dire des modèles qui prennent des modèles en tant que paramètres) pour faire une conception de classe basée sur des règles. Quels autres usages cette technique a-t-elle?Quelles sont les utilisations des paramètres de modèle de modèle en C++?
Répondre
Je pense que vous devez utiliser la syntaxe de modèle modèle pour passer un paramètre dont le type est un modèle dépendant d'un autre modèle comme celui-ci:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
Ici, H
est un modèle, mais je voulais cette fonction pour traiter avec toutes les spécialisations de H
.
NOTE: J'ai programmé C++ pendant de nombreuses années et j'en avais seulement besoin une fois. Je trouve que c'est une fonctionnalité rarement nécessaire (bien sûr 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 imaginons un exemple. Faisons semblant que std::vector
n'a pas ont un typedef value_type
.
Alors, comment écrire une fonction qui peut créer des variables de type correct pour les éléments de vecteurs? Cela fonctionnerait.
template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
// This can be "typename V<T, A>::value_type",
// but we are pretending we don't have it
T temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
NOTE: nous std::vector
a deux paramètres de modèle, le type et allocateur, donc nous avons dû accepter les deux. Heureusement, à cause de la déduction de type, nous n'aurons pas besoin d'écrire explicitement le type exact.
que vous pouvez utiliser comme ceci:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
ou mieux encore, nous pouvons simplement utiliser:
f(v); // everything is deduced, f can deal with a vector of any type!
MISE À JOUR: Même cet exemple artificiel, tout en illustration, n'est plus une exemple étonnant en raison de C++ 11 présentant auto
. Maintenant, la même fonction peut être écrite comme:
template <class Cont>
void f(Cont &v) {
auto temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
comment je préférerais écrire ce type de code.
Si f est une fonction définie par le l'utilisateur d'une bibliothèque, il est moche que l'utilisateur doit passer std :: allocator
Eh bien, vous n'avez pas à fournir l'allocateur. Ce qui est important, c'est que le paramètre du modèle de template a été défini sur le nombre correct d'arguments. Mais la fonction ne devrait pas se soucier de leurs "types" ou de leur signification, ce qui fonctionne bien en C++ 98: 'template classe C, classe T, classe U> void f (C
Je me demande pourquoi l'instanciation est 'f
Voici un exemple simple tiré de 'Modern C++ Design - Generic Programming and Design Patterns Applied' par Andrei Alexandrescu:
Il utilise un cours avec des paramètres de modèle de modèle afin de mettre en œuvre le modèle de la politique:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
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 politique. Dans l'exemple ci-dessus, WidgetManager gère toujours les objets de type Widget, ce qui oblige l'utilisateur à 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 pour spécifier des stratégies.
L'effet est que le code client peut utiliser « WidgetManager » de manière plus élégante:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
Au lieu de la plus lourde, et d'erreurs façon sujette que les arguments de modèle de modèle manquant définition aurait nécessité :
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
La question spécifiquement demandée pour les exemples autres que le modèle de politique. – user2913094
Heureux que vous avez inclus votre réponse de toute façon. Exactement ce dont j'avais besoin: D – Mordachai
Voici un autre exemple concret de mon CUDA Convolutional neural network library. J'ai le modèle de classe suivante:
template <class T> class Tensor
qui est en fait la manipulation met en œuvre des matrices n dimensions. Il y a aussi un modèle de classe enfant:
template <class T> class TensorGPU : public Tensor<T>
qui met en œuvre les mêmes fonctionnalités mais dans le GPU. Les deux modèles peuvent fonctionner avec tous les types de base, comme float, double, int, etc Et j'ai aussi un modèle de classe (simplifié):
template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
TT<T> weights;
TT<T> inputs;
TT<int> connection_matrix;
}
La raison ici pour avoir la syntaxe du modèle de modèle est parce que je peux déclarer la mise en œuvre de la classe
class CLayerCuda: public CLayerT<TensorGPU, float>
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) ou sur GPU (en spécifiant TT = TensorGPU).
Dites 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 (s):
template <typename DERIVED, typename VALUE> class interface {
void do_something(VALUE v) {
static_cast<DERIVED*>(this)->do_something(v);
}
};
template <typename VALUE> class derived : public interface<derived, VALUE> {
void do_something(VALUE v) { ... }
};
typedef interface<derived<int>, int> derived_t;
Notez la duplication des « int », qui est en fait le même paramètre de type spécifié aux deux modèles. Vous pouvez utiliser un modèle de modèle pour DÉRIVÉS pour éviter cette duplication:
template <template <typename> class DERIVED, typename VALUE> class interface {
void do_something(VALUE v) {
static_cast<DERIVED<VALUE>*>(this)->do_something(v);
}
};
template <typename VALUE> class derived : public interface<derived, VALUE> {
void do_something(VALUE v) { ... }
};
typedef interface<derived, int> derived_t;
Notez que vous éliminez fournir directement l'autre paramètre de modèle (s) au dérivé modèle; "l'interface" les reçoit toujours. Cela permet également de créer des typedefs dans l'interface qui dépend des paramètres de type, qui seront accessibles depuis le modèle dérivé.
Le typedef ci-dessus ne fonctionne pas car vous ne pouvez pas typerf pour un modèle non spécifié. Cela fonctionne, cependant (et 11 C++ a un support natif pour typedefs de modèle):
template <typename VALUE>
struct derived_interface_type {
typedef typename interface<derived, VALUE> type;
};
typedef typename derived_interface_type<int>::type derived_t;
Vous avez besoin d'un derived_interface_type pour chaque instanciation du modèle dérivé malheureusement, à moins qu'il y a une autre astuce que je ne l'ai pas encore appris.
J'avais besoin de cette solution exacte pour du code (merci!). Bien que cela fonctionne, je ne comprends pas comment la classe de modèle 'derived' peut être utilisée sans ses arguments de modèle, à savoir la ligne' typedef typename interface
@Carlton cela fonctionne essentiellement parce que le paramètre de modèle correspondant être rempli est défini comme un «modèle
En fait, l'utilisation des paramètres de modèle de modèle est plutôt évidente.Une fois que vous apprenez que le stdlib de C a trou béant de ne pas définir des opérateurs de sortie de flux pour les types de conteneurs standards, vous passez à écrire quelque chose comme:
template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
{
out << '[';
if (!v.empty()) {
for (typename std::list<T>::const_iterator i = v.begin(); ;) {
out << *i;
if (++i == v.end())
break;
out << ", ";
}
}
out << ']';
return out;
}
alors vous figurez que le code pour le vecteur est la même, pour forward_list est le même, en fait, même pour une multitude de types de cartes, c'est toujours pareil. Ces classes de modèles n'ont rien en commun à l'exception de la méta-interface/protocole, et l'utilisation du paramètre de modèle de modèle permet de capturer la communité dans chacun d'eux. Avant de procéder à l'écriture d'un modèle, il est utile 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. Alors que allocateur est réglé par défaut, il faut encore tenir compte de son existence dans notre opérateur de template < <:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
Voila, qui fonctionnera automagiquement pour tous les conteneurs de séquence présents et futurs qui adhèrent au protocole standard. Pour ajouter des cartes au mélange, il faudrait jeter un coup d'œil à la référence pour noter qu'ils acceptent 4 paramètres de modèle, donc nous aurions besoin d'une autre version de l'opérateur < < ci-dessus avec 4-arg template template param. Nous verrons aussi que std: pair essaie d'être rendu avec l'opérateur 2-arg < < pour les types de séquences que nous avons définis précédemment, donc nous fournirions une spécialisation juste pour std :: pair. Avec un C + 11 qui permet des gabarits variés (et qui devrait donc permettre des gabarits de gabarits variés), il serait possible d'avoir un seul opérateur < < pour tout les gouverner. Par exemple:
#include <iostream>
#include <vector>
#include <deque>
#include <list>
template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
os << __PRETTY_FUNCTION__ << '\n';
for (auto const& obj : objs)
os << obj << ' ';
return os;
}
int main()
{
std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
std::cout << vf << '\n';
std::list<char> lc { 'a', 'b', 'c', 'd' };
std::cout << lc << '\n';
std::deque<int> di { 1, 2, 3, 4 };
std::cout << di << '\n';
return 0;
}
Sortie
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
1.1 2.2 3.3 4.4
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
a b c d
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
1 2 3 4
Il s'agit d'un exemple de paramètres de modèle de gabarit, car il montre un cas auquel tout le monde a dû faire face. – Ravenwater
+1, a ajouté l'exemple C++ 11 en utilisant des modèles variadiques. – WhozCraig
C'est la réponse la plus éveillée pour moi dans les modèles C++. @WhozCraig Comment avez-vous obtenu les détails de l'extension du modèle? – Arun
C'est ce que je suis tombé:
template<class A>
class B
{
A& a;
};
template<class B>
class A
{
B b;
};
class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{
};
peut être résolu à:
template<class A>
class B
{
A& a;
};
template< template<class> class B>
class A
{
B<A> b;
};
class AInstance : A<B> //happy
{
};
ou (code de travail):
template<class A>
class B
{
public:
A* a;
int GetInt() { return a->dummy; }
};
template< template<class> class B>
class A
{
public:
A() : dummy(3) { b.a = this; }
B<A> b;
int dummy;
};
class AInstance : public A<B> //happy
{
public:
void Print() { std::cout << b.GetInt(); }
};
int main()
{
std::cout << "hello";
AInstance test;
test.Print();
}
Dans la solution avec des modèles variadique fournis par pfalcon, je l'ai trouvé difficile de se spécialiser en fait l'opérateur ostream pour std :: carte en raison de la nature avide de la spécialisation variadique. Voici une légère révision qui a fonctionné pour moi:
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>
namespace containerdisplay
{
template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
for (auto const& obj : objs)
os << obj << ' ';
return os;
}
}
template< typename K, typename V>
std::ostream& operator << (std::ostream& os,
const std::map< K, V > & objs)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
for(auto& obj : objs)
{
os << obj.first << ": " << obj.second << std::endl;
}
return os;
}
int main()
{
{
using namespace containerdisplay;
std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
std::cout << vf << '\n';
std::list<char> lc { 'a', 'b', 'c', 'd' };
std::cout << lc << '\n';
std::deque<int> di { 1, 2, 3, 4 };
std::cout << di << '\n';
}
std::map< std::string, std::string > m1
{
{ "foo", "bar" },
{ "baz", "boo" }
};
std::cout << m1 << std::endl;
return 0;
}
Voici une généralisée de quelque chose que je viens d'utiliser. Je poste depuis qu'il est un très exemple simple, et il démontre un cas d'utilisation pratique ainsi que des arguments par défaut:
#include <vector>
template <class T> class Alloc final { /*...*/ };
template <template <class T> class allocator=Alloc> class MyClass final {
public:
std::vector<short,allocator<short>> field0;
std::vector<float,allocator<float>> field1;
};
Il améliore la lisibilité de votre code, offre une sécurité de type supplémentaire et économiser des efforts du compilateur.
que vous voulez imprimer chaque élément d'un conteneur, vous pouvez utiliser le code suivant sans paramètre de modèle de modèle
template <typename T> void print_container(const T& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
ou avec le paramètre de modèle de modèle
template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
On suppose que vous passez dans un entier dites print_container(3)
. Dans le premier cas, le modèle sera instancié par le compilateur qui se plaindra de l'utilisation de c
dans la boucle for, ce dernier n'instanciera pas du tout le modèle car aucun type correspondant ne peut être trouvé. En règle 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 rendre clair.
- 1. Combien et quelles sont les utilisations de "const" en C++?
- 2. Quelles sont les utilisations légitimes des crochets de clavier globaux?
- 3. Quelles sont les utilisations des métadonnées de Clojure?
- 4. Extraire les paramètres du modèle C++
- 5. Quelles sont les utilisations intéressantes pour les agents Java?
- 6. Quelles sont les utilisations pratiques du fichier resx?
- 7. Comment déclarer/définir une classe avec des paramètres de modèle de modèle sans l'aide d'un paramètre de modèle supplémentaire
- 8. Typedef une classe de modèle sans spécifier les paramètres de modèle
- 9. C++: paramètres de modèle variable (pour l'algorithme génétique)
- 10. Paramètres de modèle pour l'enregistreur Entlib
- 11. Paramètres de paramètres de modèle par défaut dans VC++
- 12. Passer des paramètres à un modèle
- 13. spécialisation de modèle de classe modèle
- 14. Quelles sont vos utilisations les plus courantes pour les expressions régulières?
- 15. MVC en C#: Modèle - relation de contrôleur
- 16. Quels sont les problèmes du modèle MVVM?
- 17. Paramètre de modèle non-type ... c'est un modèle! (C++)
- 18. C++ fonction de modèle SURCHARGE
- 19. Quels sont les avantages d'un modèle de données XML sur le modèle DataSet?
- 20. Les requêtes de modèle Django asynchrones sont-elles possibles?
- 21. Comportement étrange des arguments de modèle dans Visual C++?
- 22. modèle de C raccourci macro
- 23. C++ spécialisation de modèle de fonction: "l'utilisation illégale des arguments de modèle explicite"
- 24. Où sont les utilisations intelligentes de l'évaluation stricte?
- 25. Modèle MVC + modèle DDD
- 26. MVC modèle/héritage modèle
- 27. Comment maintenir la cohérence entre le modèle et le modèle de vue dans le modèle MVVM?
- 28. Lors des transmissions sur localhost, quelles couches sont utilisées dans le modèle OSI?
- 29. Amitié de modèle
- 30. Quelles sont les longueurs des types de données communs?
Je suis venu de l'autre direction (FP, Haskell, etc.) et j'ai atterri dessus: http://stackoverflow.com/questions/2565097/higher-kinded-types-with-c –