276 votes

Quelles sont les utilisations d'un gabarit ?

J'ai vu quelques exemples de C++ utilisant des paramètres de modèles (c'est-à-dire des modèles qui prennent des modèles comme paramètres) pour concevoir des classes basées sur des règles. Quelles sont les autres utilisations de cette technique ?

5 votes

I s

229voto

Evan Teran Points 42370

Je pense que vous devez utiliser le modèle de la syntaxe du modèle de passer un paramètre dont le type est un modèle de la personne à charge sur un autre modèle comme ceci:

template<template<class> class H, class S>
void f(const H<S> &value) {
}

Ici, H est un type qui est basé sur un modèle, mais je voulais cette fonction pour traiter toutes les spécialisations de la H.

REMARQUE: j'ai été à la programmation c++ depuis de nombreuses années et ont seulement besoin d'une fois, je trouve qu'il est rarement nécessaire de fonctionnalité (bien sûr, à portée de main lorsque vous en avez besoin!).

EDIT: j'ai essayé de penser à de bons exemples, et pour être honnête, la plupart du temps ce n'est pas nécessaire, mais nous allons inventer un exemple. Imaginons qu' std::vector ne pas avoir un typedef value_type. Alors, comment voulez-vous écrire une fonction qui peut créer des variables de type pour les vecteurs éléments? Cela aurait fonctionné.

template<template<class> class V, class T>
void f(const V<T> &v) {
    // this can be V::value_type, but we are pretending we don't have it
    T temp = v.back();
    // do some work on temp
}

En fait, cela ne fonctionne pas avec std::vector parce qu'il a 2 modèle params pas 1, mais si nous avions une classe avec la même interface que std::vector mais n'ont pas d' value_type, alors ce serait un moyen de connaître le type à l'intérieur de la fonction.

Enfin, voici une version qui ne de travailler avec std::vector:

template<template<class, class> class V, class T>
void f(V<T, std::allocator<T> > &v) {
    // this can be "typename V<T, std::allocator<T> >::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;
}

que vous pouvez utiliser comme ceci:

f<std::vector, int>(v); // v is of type std::vector<int> using the standard allocator

1 votes

Si f est une fonction définie par l'utilisateur d'une bibliothèque, il est moche que l'utilisateur doive passer std::allocator<T> comme argument. Je me serais attendu à ce que la version sans l'argument std::allocator fonctionne en utilisant le paramètre par défaut de std::vector. Y a-t-il des mises à jour à ce sujet par rapport à C++0x ?

1 votes

Il n'est pas nécessaire de fournir un allocateur. Ce qui est important, c'est que le paramètre du modèle soit défini sur un nombre correct d'arguments. Mais la fonction ne doit pas se préoccuper de leurs "types" ou de leur signification, ce qui fonctionne bien en C++98 : template<template<class, class> class C, class T, class U> void f(C<T, U> &v)

0 votes

Je me demande pourquoi l'instanciation est f<vector,int> et non f<vector<int>> .

176voto

pfalcon Points 1185

En fait, le cas d'utilisation des paramètres de gabarit est assez évident. Une fois que vous avez appris que la stdlib C++ a un trou béant en ne définissant pas les opérateurs de sortie de flux pour les types de conteneurs standard, vous pouvez é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;
}

Vous vous apercevrez alors que le code pour vector est le même, pour 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 d'un paramètre de modèle de modèle permet de capturer les points communs à toutes ces classes. Avant de procéder à l'écriture d'un modèle, il est utile de vérifier une référence pour se rappeler que les conteneurs de séquence acceptent deux arguments de modèle - pour le type de valeur et l'allocateur. Bien que l'allocateur soit proposé par défaut, nous devons tout de même tenir compte de son existence dans notre opérateur de gabarit<< :

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

Voilà, cela fonctionnera automatiquement pour tous les conteneurs de séquence présents et futurs qui adhèrent au protocole standard. Pour ajouter les cartes au mélange, il suffirait de jeter un coup d'œ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 un paramètre de modèle de 4args. Nous verrions également que std:pair essaie d'être rendu avec un opérateur<< à 2 arguments pour les types de séquence que nous avons définis précédemment, nous fournirions donc une spécialisation juste pour std::pair.

Par ailleurs, avec C+11 qui autorise les modèles variadiques (et qui devrait donc autoriser les arguments variadiques des modèles), il serait possible d'avoir un seul opérateur<< pour les dominer tous. 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

15 votes

Il s'agit là d'un très bon exemple de paramètres de modèles, car il s'agit d'un cas auquel tout le monde a été confronté.

4 votes

C'est la réponse qui m'a le plus réveillé dans les templates C++. @WhozCraig Comment avez-vous obtenu les détails de l'expansion des templates ?

4 votes

@Arun gcc supporte une macro appelée __PRETTY_FUNCTION__ qui, entre autres choses, rapporte les descriptions des paramètres des modèles en texte clair. clang le fait également. Une fonctionnalité très pratique parfois (comme vous pouvez le voir).

72voto

yoav.aviram Points 1014

Voici un exemple simple tiré de Modern C++ Design - Generic Programming and Design Patterns Applied" (Conception moderne du C++ - Programmation générique et modèles de conception appliqués) par Andrei Alexandrescu :

Il utilise une classe avec des paramètres de modèle afin de mettre en œuvre le modèle de politique :

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

Il explique : Généralement, la classe hôte connaît déjà, ou peut facilement déduire, le modèle d'argument de la classe de politique. Dans l'exemple ci-dessus, WidgetManager gère toujours des objets de type Widget, de sorte que demander à l'utilisateur de spécifier à nouveau Widget dans l'instanciation de CreationPolicy est redondant et potentiellement dangereux.Dans ce cas, le code de la bibliothèque peut utiliser des paramètres de modèle pour spécifier les politiques.

Le code client peut ainsi utiliser le "WidgetManager" de manière plus élégante :

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

Au lieu de la méthode plus lourde et sujette à erreur qu'aurait exigée une définition dépourvue d'arguments de gabarit :

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;

2 votes

La question demandait expressément des exemples autres que le modèle de politique.

0 votes

C'est justement ce livre qui m'a amené à cette question. Il est intéressant de noter que les paramètres du modèle apparaissent également dans le chapitre Typelist et le chapitre Génération de classes avec des listes de types chapitre.

19voto

Mikhail Sirotenko Points 101

Voici un autre exemple pratique tiré de mon Bibliothèque CUDA pour réseaux neuronaux convolutifs . J'ai le modèle de classe suivant :

template <class T> class Tensor

qui permet en fait de manipuler des matrices à n dimensions. Il existe également un modèle de classe enfant :

template <class T> class TensorGPU : public Tensor<T>

qui implémente la même fonctionnalité mais dans le GPU. Les deux modèles peuvent fonctionner avec tous les types de base, comme float, double, int, etc. J'ai également 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 pour laquelle la syntaxe des modèles est utilisée est que je peux déclarer l'implémentation de la classe

class CLayerCuda: public CLayerT<TensorGPU, float>

qui aura 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).

0 votes

Pouvez-vous forcer la déduction de T avec quelque chose comme : "template <classe T, template <T> TT> CLayerT" et "class CLayerCuda : public CLayerT<TensorGPU<float>>" ? Au cas où vous n'auriez pas besoin d'un TT<autreT>

0 votes

NE JAMAIS S'EN SOUVENIR : template<template<classe T> class U> class B1 { } ; from ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/ D'après une recherche rapide sur Google

12voto

Mark McKenna Points 915

Supposons que vous utilisiez le CRTP pour fournir une "interface" à un ensemble de modèles enfants, et que le parent et l'enfant soient paramétrés par d'autres arguments du modèle :

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 de "int", qui est en fait le même paramètre de type spécifié dans les deux modèles. Vous pouvez utiliser un modèle pour DERIVED afin d'é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 le fait de fournir directement le(s) autre(s) paramètre(s) du modèle à la fonction dérivés L'"interface" les reçoit toujours.

Cela vous permet également de construire 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 parce qu'on ne peut pas typedef vers un modèle non spécifié. Ceci fonctionne cependant (et C++11 a un support natif pour les typedefs de templates) :

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

Il faut un derived_interface_type pour chaque instanciation du modèle dérivé, malheureusement, à moins qu'il y ait une autre astuce que je n'ai pas encore apprise.

0 votes

J'avais besoin de cette solution exacte pour un code (merci !). Bien que cela fonctionne, je ne comprends pas comment la classe template derived peut être utilisé sans ses arguments de modèle, c'est-à-dire la ligne typedef typename interface<derived, VALUE> type;

0 votes

@Carlton cela fonctionne essentiellement parce que le paramètre du modèle correspondant qui est rempli est défini en tant que template <typename> . D'une certaine manière, on peut considérer que les paramètres des modèles ont un "métatype" ; le métatype normal d'un paramètre de modèle est le suivant typename ce qui signifie qu'il doit être rempli par un type régulier ; le type template signifie qu'il doit être rempli avec une référence à un modèle. derived définit un modèle qui accepte un typename il correspond donc à ce que l'on attend de lui et peut être référencé ici. Cela a-t-il un sens ?

0 votes

Le C++11 n'a pas encore été adopté typedef . Vous pouvez également éviter les doublons. int dans votre premier exemple en utilisant une construction standard telle qu'un value_type dans le type DERIVED.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X