Chapitre 8 – Les templates
Document réservé
Vous consultez actuellement la présentation publique de ce chapitre.
Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.
Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).
8.1.1 La même opération avec un autre type . . . . . . . . . . . . . . . . . . . . . .
8.1.2 Une répétition inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.1.3 Séparer l’algorithme du type . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2 Premier template de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.4 Le paramètre de type T . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.6 Utilisation avec un autre type . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3.1 Un paramètre représentant un type . . . . . . . . . . . . . . . . . . . . . . . .
8.3.2 T est utilisé comme un type . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3.3 T peut représenter différents types . . . . . . . . . . . . . . . . . . . . . . . .
8.3.4 Le type doit permettre les opérations utilisées . . . . . . . . . . . . . . . . . .
8.3.5 Le nom du paramètre de type . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.4 Instanciation d’un template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.4.1 Une première instanciation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.4.2 Plusieurs instanciations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.4.3 Une instanciation par type nécessaire . . . . . . . . . . . . . . . . . . . . . . .
8.4.4 Uniquement les versions nécessaires . . . . . . . . . . . . . . . . . . . . . . . .
8.4.5 Indiquer explicitement le type . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.5 Déduction automatique du type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.5.1 Le type peut être déduit des arguments . . . . . . . . . . . . . . . . . . . . .
8.5.2 Un même template, plusieurs déductions . . . . . . . . . . . . . . . . . . . . .
8.5.3 Lorsque les types ne correspondent pas . . . . . . . . . . . . . . . . . . . . . .
8.5.4 Lever l’ambiguïté par une conversion explicite . . . . . . . . . . . . . . . . . .
8.5.5 Indiquer explicitement le type du template . . . . . . . . . . . . . . . . . . . .
8.5.6 Le type de retour ne participe pas à cette déduction . . . . . . . . . . . . . .
8.6 Plusieurs paramètres de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2026 – Partie II C++
8.6.1 Déclarer plusieurs paramètres de type . . . . . . . . . . . . . . . . . . . . . . .
8.6.2 Un nouveau problème : le type de retour . . . . . . . . . . . . . . . . . . . . .
8.6.3 Déduction automatique du type de retour . . . . . . . . . . . . . . . . . . . .
8.6.4 Plusieurs déductions dans une même instruction . . . . . . . . . . . . . . . .
8.6.5 Un seul type peut constituer une contrainte volontaire . . . . . . . . . . . . .
8.7 Les exigences imposées aux types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.7.1 Des exigences parfois implicites . . . . . . . . . . . . . . . . . . . . . . . . . .
8.7.2 Les types fondamentaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.7.3 Templates et surcharge des opérateurs . . . . . . . . . . . . . . . . . . . . . .
8.7.4 Le template ne connaît pas à l’avance les classes utilisées . . . . . . . . . . .
8.7.5 Des exigences exprimées par le code . . . . . . . . . . . . . . . . . . . . . . .
8.7.6 Éviter les copies inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.7.7 Vers les concepts du C++ moderne . . . . . . . . . . . . . . . . . . . . . . .
8.8.1 Une classe liée à un type particulier . . . . . . . . . . . . . . . . . . . . . . . .
8.8.2 Transformer la classe en template . . . . . . . . . . . . . . . . . . . . . . . . .
8.8.3 Instanciation d’une classe template . . . . . . . . . . . . . . . . . . . . . . . .
8.8.5 Template et occupation mémoire . . . . . . . . . . . . . . . . . . . . . . . . .
8.8.7 Utiliser une classe comme argument de type . . . . . . . . . . . . . . . . . . .
8.8.8 Les exigences imposées au type restent présentes . . . . . . . . . . . . . . . .
8.8.9 Vers une classe Matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.9 Plusieurs paramètres de type dans une classe . . . . . . . . . . . . . . . . . . . . . . .
8.9.1 Deux types indépendants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.9.2 Chaque combinaison constitue un type distinct . . . . . . . . . . . . . . . . .
8.10 Séparer déclaration et définition d’une classe template . . . . . . . . . . . . . . . . .
8.10.1 Déclaration de la classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.10.2 Définition des fonctions membres . . . . . . . . . . . . . . . . . . . . . . . . .
8.10.3 Pourquoi un fichier .cpp séparé pose-t-il un problème ? . . . . . . . . . . . .
8.10.4 Une première solution : tout placer dans le fichier d’en-tête . . . . . . . . . .
8.10.5 Séparer les définitions dans un fichier .tpp . . . . . . . . . . . . . . . . . . .
8.10.6 Pourquoi inclure le fichier .tpp à la fin ? . . . . . . . . . . . . . . . . . . . . .
8.10.7 Le fichier .tpp n’est pas un fichier .cpp . . . . . . . . . . . . . . . . . . . . .
8.10.8 Organisation du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . .
AccueilPourquoi les templates ? Supposons que nous voulions écrire une fonction retournant la plus grande de deux valeurs entières. Nous pouvons simplement écrire : int maximum(int a, int b) Nous pouvons alors l’utiliser de la manière habituelle : int resultat = maximum(5, 8); La fonction reçoit deux valeurs de type int et retourne également une valeur de type int. La même opération avec un autre type Nous souhaitons maintenant effectuer exactement la même opération avec deux valeurs de type double. La fonction précédente ne correspond plus exactement aux types que nous voulons manipuler. Nous pouvons naturellement profiter de la surcharge de fonctions et ajouter une seconde version : double maximum(double a, double b) 2026 – Partie II C++ Ces deux fonctions sont parfaitement valides. Mais observons-les attentivement. Le traitement effectué est exactement le même : • comparer les deux arguments avec l’opérateur > ; • retourner le premier si celui-ci est le plus grand ; • retourner le second dans le cas contraire. La seule différence entre les deux fonctions est le type utilisé. On pourrait imaginer avoir maximum(double, double) maximum(float, float) Une répétition inutile Nous pourrions continuer à surcharger la fonction maximum() pour tous les types dont nous avons besoin. Cette solution présente cependant un défaut évident : nous recopions plusieurs fois le même algorithme uniquement parce que le type des données change. Nous aimerions pouvoir exprimer quelque chose comme : « Voici l’algorithme de maximum(). Utilise-le avec le type dont j’aurai besoin. » Séparer l’algorithme du type Le C++ permet de définir un modèle de fonction dans lequel certains types ne sont pas encore fixés. Ce modèle porte le nom de template. Au lieu d’écrire séparément : int maximum(int a, int b); double maximum(double a, double b); float maximum(float a, float b); nous allons pouvoir écrire une seule définition capable de servir de modèle pour plusieurs types. Le type nécessaire sera déterminé lorsque le template sera utilisé. Il ne faut cependant pas imaginer qu’un template crée une fonction manipulant un mystérieux « type universel ». Le C++ reste fortement typé. Lorsqu’une version du template est nécessaire pour un type donné, le compilateur travaille avec ce type précis. Nous verrons que les templates constituent donc essentiellement un mécanisme permettant au compilateur de produire du code adapté aux types utilisés.
Accueil2026 – Partie II C++ • La surcharge permet d’écrire plusieurs fonctions portant le même nom mais utilisant des • Lorsque seul le type change et que l’algorithme reste identique, cette solution peut conduire à recopier plusieurs fois le même code. • Un template permet de définir un modèle dans lequel certains types ne sont pas encore • Le type concret sera déterminé lors de l’utilisation du template. • Les templates ne suppriment pas le typage fort du C++ : le compilateur travaille finalement avec des types parfaitement déterminés. Premier template de fonction Nous avons vu que plusieurs fonctions peuvent posséder exactement le même algorithme tout en ne différant que par le type de leurs paramètres. int maximum(int a, int b) double maximum(double a, double b) réalisent exactement la même opération. Nous allons maintenant remplacer ces différentes fonctions par un seul modèle. Nous pouvons écrire : template <typename T>
Accueil2026 – Partie II C++ Cette déclaration introduit notre premier template de fonction. Le mot-clé template template <typename T> indique que la définition qui suit est un template. Autrement dit, nous ne définissons pas encore une fonction utilisant un type précis comme int ou double. Nous définissons un modèle à partir duquel le compilateur pourra produire des fonctions utilisant contiennent les paramètres du template. Dans notre exemple : déclare un paramètre représentant un type. Le mot-clé typename indique que le paramètre qui suit représente un type. T représente donc un type qui sera déterminé lors de l’utilisation du template. Le paramètre de type T Le symbole T est simplement un nom choisi par le programmeur. Par convention, la lettre T est souvent utilisée pour représenter un type générique. Nous pourrions cependant écrire, par exemple : template <typename Type> Type maximum(Type a, Type b)
Accueil2026 – Partie II C++ Le fonctionnement serait identique. La lettre T ne possède donc aucune signification particulière dans • la fonction reçoit deux paramètres du même type T ; • elle retourne également une valeur de type T. Le type T n’est pas encore fixé lors de l’écriture du template. Première utilisation Considérons maintenant : Les deux arguments transmis à maximum() sont de type int. Le compilateur peut donc déterminer que, pour cet appel, Le template peut alors être compris comme un modèle permettant au compilateur de produire une fonction équivalente à : int maximum(int a, int b) Cette fonction n’a pas été écrite directement par le programmeur. Elle est obtenue à partir du template pour le type nécessaire. Utilisation avec un autre type Nous pouvons ensuite écrire :
Accueil2026 – Partie II C++ Cette fois, les deux arguments sont de type double. Le compilateur détermine donc : Il peut produire une version correspondant à : double maximum(double a, double b) Nous avons donc écrit une seule fois l’algorithme : template <typename T> mais ce modèle peut être utilisé avec plusieurs types. • Un template de fonction commence par une déclaration de la forme : template <typename T> • T représente un type qui n’est pas encore fixé lors de l’écriture du template. • Lors de l’utilisation de la fonction, le compilateur détermine le type nécessaire à partir • Pour un appel avec deux int, le compilateur utilise T = int. • Pour un appel avec deux double, il utilise T = double. • Le template constitue donc un modèle permettant de produire des fonctions adaptées aux types réellement utilisés. Le paramètre de type T Dans notre premier template, nous avons écrit : template <typename T>
Accueil2026 – Partie II C++ La lettre T apparaît à plusieurs endroits dans cette définition. Il est important de comprendre précisément ce qu’elle représente. Un paramètre représentant un type Dans une fonction classique : n est un paramètre de la fonction. Sa valeur n’est pas connue lors de l’écriture de la fonction. Elle sera fournie lors de l’appel : template <typename T> T joue également le rôle d’un paramètre, mais il ne représente pas une valeur. Il représente un type. Fig. 8.1 – Représentation d’un type T et d’une variable n. T est utilisé comme un type Une fois déclaré par : template <typename T> T peut être utilisé dans la définition du template aux endroits où un type est attendu. le premier T indique le type de retour de la fonction. Les deux suivants indiquent les types des paramètres a et b. Fig. 8.2 – Schéma Le même paramètre de type est utilisé plusieurs fois dans la déclaration.
Accueil2026 – Partie II C++ Lorsque le compilateur détermine la valeur de T, toutes les occurrences de T correspondent au même conduit à une fonction correspondant à : double maximum(double a, double b); T peut représenter différents types Le paramètre T n’est pas réservé aux types fondamentaux du C++. Il peut par exemple représenter : mais il peut également représenter un type défini par le programmeur, comme une classe. Un template peut donc être utilisé aussi bien avec des types fournis par le langage qu’avec nos propres Le type doit permettre les opérations utilisées Un template ne peut pas effectuer magiquement une opération qui n’existe pas pour le type utilisé. Notre fonction contient : Le type représenté par T doit donc permettre l’utilisation de l’opérateur > entre les deux objets. Cette condition est naturellement satisfaite pour : car l’opérateur > est défini pour ces types. Si nous voulions utiliser le même template avec une classe créée par le programmeur, l’opération utilisée par le template devrait également être définie pour cette classe. Nous retrouvons ici directement la notion de surcharge d’opérateur étudiée précédemment. Le nom du paramètre de type La lettre T est une convention très répandue, mais elle n’est pas imposée par le C++. Nous pourrions écrire : template <typename Type> Type maximum(Type a, Type b)
Accueil2026 – Partie II C++ template <typename Nombre> Nombre maximum(Nombre a, Nombre b) Pour le compilateur, le principe est exactement le même. Pour déclarer un paramètre de type, le C++ permet également l’écriture : Dans ce contexte, elle est équivalente à : template <typename T> Le mot class ne signifie pas ici que T doit obligatoirement représenter une classe. peut parfaitement être utilisé avec T = int. Dans ce cours, nous utiliserons principalement typename, qui exprime directement que le paramètre représente un type.
Accueil2026 – Partie II C++ template <typename T> T est un paramètre représentant un type. • Contrairement au paramètre d’une fonction classique, il ne représente pas une valeur mais un type utilisé lors de la compilation. • Toutes les occurrences de T correspondent au même type pour une instanciation donnée • T peut représenter un type fondamental ou un type défini par le programmeur. • Le type utilisé doit permettre les opérations effectuées dans le template. • Le nom T est une convention et non un mot réservé du C++. • Dans une déclaration de paramètre de type, typename T et class T sont équivalents. Instanciation d’un template Lorsque nous écrivons : template <typename T> nous définissons un modèle de fonction. À ce stade, aucune valeur particulière n’est associée au paramètre de type T. C’est l’utilisation du template avec un type concret qui va conduire le compilateur à produire la fonction correspondante. Ce mécanisme porte le nom d’instanciation du template. Une première instanciation les deux arguments sont de type int. Le compilateur détermine donc que le paramètre de type T doit correspondre à int. Il instancie alors une version du template pour ce type. Nous pouvons nous représenter cette version comme étant équivalente à : int maximum(int a, int b)
Accueil2026 – Partie II C++ Plusieurs instanciations Si le même programme contient : avec a et b de type int, puis x et y de type double, le compilateur a besoin de deux versions différentes du template. Nous pouvons les représenter schématiquement par : int maximum(int a, int b); double maximum(double a, double b); Une même définition de template conduit donc ici à deux instanciations. Une instanciation par type nécessaire Plusieurs appels utilisant le même type ne nécessitent pas une nouvelle version du template pour utilisent tous le template avec le type int. Il suffit donc d’une version correspondant à ce type. Il faut distinguer : l’appel d’une fonction l’instanciation d’un template pour un type donné. Uniquement les versions nécessaires La définition d’un template ne provoque pas la création de versions pour tous les types imaginables. Le compilateur instancie les versions nécessaires en fonction de l’utilisation qui est faite du template. Si notre programme utilise uniquement : il n’a aucune raison d’instancier une version pour double, float ou une classe créée par le programmeur. Les versions nécessaires apparaissent donc en fonction des types avec lesquels le template est effectivement utilisé.
Accueil2026 – Partie II C++ Indiquer explicitement le type Il est également possible d’indiquer explicitement le type avec lequel nous voulons utiliser le template : Dans cette écriture : indique que le paramètre de type T doit correspondre à int. maximum<double>(5.2, 8.7); Il existe ici une analogie avec les paramètres et les arguments d’une fonction ordinaire. template <typename T> T est un paramètre de template. int est un argument de template. • La définition d’un template constitue un modèle à partir duquel le compilateur peut produire des fonctions concrètes. • La production d’une version du template pour un type donné porte le nom • Un template peut être instancié pour plusieurs types différents dans un même programme. • Plusieurs appels utilisant le même type peuvent utiliser la même instanciation du template. • Le compilateur ne produit pas toutes les versions imaginables du template, mais les versions nécessaires à son utilisation. • Le type peut être indiqué explicitement : • Dans template <typename T>, T est un paramètre de template, tandis que dans maximum<int>(), int est un argument de template.
Accueil2026 – Partie II C++ Déduction automatique du type Nous avons vu qu’un template de fonction permet de définir un modèle générique : template <typename T> return (a > b) ? a : b; Nous pouvons appeler explicitement cette fonction en indiquant le type souhaité : int resultat = maximum<int>(5, 8); Le compilateur utilise alors int à la place du paramètre de type T. Le type peut être déduit des arguments Dans de nombreux appels de fonctions templates, il n’est pas nécessaire d’indiquer explicitement le type utilisé pour remplacer le paramètre T. int resultat = maximum(5, 8); Les deux arguments : Le compilateur compare alors les types des arguments avec les paramètres du template : Il peut ensuite instancier la fonction correspondante. Un même template, plusieurs déductions Le même template peut naturellement conduire à différentes instanciations : int a = maximum(5, 8); double b = maximum(5.2, 8.7); char c = maximum(’a’, ’z’); Le compilateur peut successivement déduire :
Accueil2026 – Partie II C++ À partir d’un seul modèle de fonction, plusieurs fonctions concrètes peuvent donc être instanciées. Lorsque les types ne correspondent pas Considérons maintenant : Le premier argument est de type int : Le second est de type double : Or notre template utilise un seul paramètre de type T pour les deux arguments : Le compilateur devrait donc déduire simultanément deux types différents pour le même paramètre T. La déduction échoue. Lever l’ambiguïté par une conversion explicite Nous pouvons rendre les types des deux arguments compatibles avant que la déduction du template double resultat = maximum( static_cast<double>(5), Le compilateur considère maintenant deux arguments de type double. Il peut donc déduire sans ambiguïté : Le transtypage intervient ici avant la déduction du paramètre du template. Indiquer explicitement le type du template Nous pouvons également écrire : double resultat = maximum<double>(5, 8.7); Cette fois, nous indiquons explicitement : Le compilateur n’a donc plus à déduire T à partir des deux arguments. La fonction instanciée possède des paramètres de type double : double maximum(double a, double b); La valeur entière 5 peut alors être convertie en double pour être transmise au premier paramètre.
Accueil2026 – Partie II C++ Le type de retour ne participe pas à cette déduction double resultat = maximum(5, 8); Le type double de resultat ne conduit pas ici à déduire : La déduction est effectuée à partir des arguments transmis aux paramètres du template. Comme 5 et 8 sont de type int, nous obtenons : La fonction instanciée retourne donc un int. Cette valeur est ensuite convertie en double pour initialiser resultat. template <typename T> return (a > b) ? a : b; << maximum(5.2, 8.7) static_cast<double>(5), << maximum<double>(5, 8.7)
Accueil2026 – Partie II C++ • Lors de l’appel d’une fonction template, le compilateur peut souvent déduire automatiquement le paramètre de type à partir des arguments. les deux arguments sont de type int. Le compilateur peut donc déduire : • Il n’est alors pas nécessaire d’écrire explicitement : • Si plusieurs paramètres dépendent du même paramètre de type T, les déductions obtenues doivent être compatibles. échoue, car les arguments conduisent ici à déduire à la fois int et double pour le même • Une conversion explicite peut être effectuée avant l’appel afin de rendre les types compatibles : maximum(static_cast<double>(5), 8.7) • Il est également possible d’imposer explicitement le paramètre du template : maximum<double>(5, 8.7) • Le type de la variable recevant le résultat ne détermine pas, dans notre exemple, le type T déduit à partir des arguments. Plusieurs paramètres de type Notre première fonction template utilise un seul paramètre de type : template <typename T> return (a > b) ? a : b; Cette conception impose que les deux paramètres a et b permettent de déduire un même type T. fonctionne puisque les deux arguments sont de type int.
Accueil2026 – Partie II C++ ne permet pas de déduire un type unique pour T. Déclarer plusieurs paramètres de type Un template peut posséder plusieurs paramètres de type. template <typename T, typename U> déclare deux paramètres de type indépendants : Ils peuvent représenter le même type ou des types différents selon l’utilisation du template. Un nouveau problème : le type de retour Considérons une première généralisation : template <typename T, typename U> return (a > b) ? a : b; permet maintenant de déduire : La comparaison entre les deux valeurs est possible. Cependant, le type de retour de la fonction a été La fonction doit donc retourner un int. Choisir systématiquement le type du premier ou du second argument ne constitue donc pas une solution générale. Le type du résultat devrait dépendre de l’expression réellement retournée, et non de la position arbitraire de l’un des arguments. Déduction automatique du type de retour En C++ moderne, nous pouvons également demander au compilateur de déduire le type de retour template <typename T, typename U> auto maximum(T a, U b) return (a > b) ? a : b; Le mot-clé auto indique ici que le type de retour sera déduit à partir de l’expression retournée.
Accueil2026 – Partie II C++ Plusieurs déductions dans une même instruction auto resultat = maximum(5, 8.7); Plusieurs mécanismes de déduction interviennent successivement. Pour l’appel du template, le compilateur déduit : à partir de 5, et : Il détermine ensuite le type de l’expression retournée par la fonction et peut en déduire le type de retour indiqué par auto. Enfin, le auto utilisé pour déclarer resultat est lui-même déduit à partir de la valeur retournée par la fonction. Fig. 8.3 – Diagramme template <typename T, typename U> auto maximum(T a, U b) return (a > b) ? a : b; auto a = maximum(5, 8);
Accueil2026 – Partie II C++ auto b = maximum(5.2, 8.7); auto c = maximum(5, 8.7); auto d = maximum(8.7, 5); std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; std::cout << d << std::endl; La présence de deux paramètres de type indépendants n’impose pas que les types correspondants le compilateur peut déduire : Les paramètres de type sont indépendants, mais ils peuvent naturellement recevoir le même type. Un seul type peut constituer une contrainte volontaire Utiliser plusieurs paramètres de type rend un template plus général, mais cela ne signifie pas que cette solution soit toujours préférable. template <typename T> exprime une contrainte importante : les deux paramètres doivent correspondre au même type T lors de la déduction. Cette contrainte peut être parfaitement volontaire. template <typename T, typename U> auto maximum(T a, U b) autorise deux types indépendants. Le choix dépend donc du contrat que nous souhaitons donner à
Accueil2026 – Partie II C++ • Un template peut posséder plusieurs paramètres de type : template <typename T, typename U> • Les paramètres de type sont indépendants et peuvent représenter des types différents template <typename T, typename U> auto maximum(T a, U b) permet de déduire : • Choisir arbitrairement T ou U comme type de retour peut entraîner une conversion indésirable du résultat. • Le mot-clé auto peut permettre au compilateur de déduire le type de retour d’une fonction à partir de l’expression retournée. • La déduction des paramètres du template et la déduction du type de retour sont des mécanismes distincts. • Plusieurs paramètres de type offrent davantage de généralité, mais un template ne doit accepter que les combinaisons de types qui ont un sens pour l’opération considérée. Les exigences imposées aux types Un template permet d’écrire un algorithme sans fixer à l’avance tous les types avec lesquels il sera Cela ne signifie cependant pas que n’importe quel type pourra être substitué à un paramètre de type. Considérons notre fonction : template <typename T, typename U> auto maximum(T a, U b) return (a > b) ? a : b; Pour que cette fonction puisse être instanciée avec deux types donnés, toutes les opérations effectuées dans son corps doivent avoir un sens pour ces types. Des exigences parfois implicites Lors de l’écriture d’un template, les opérations présentes dans son corps imposent implicitement certaines exigences aux types utilisés.
Accueil2026 – Partie II C++ return (a > b) ? a : b; doit tout d’abord être valide. Les types représentés par T et U doivent donc permettre cette comparaison. L’expression conditionnelle : doit également pouvoir produire un résultat dont le type peut être déterminé. Fig. 8.4 – Schéma. Les types fondamentaux Avec les types numériques fondamentaux, de nombreuses opérations sont déjà définies par le langage. conduit à utiliser les types : L’opérateur > peut être utilisé entre ces deux valeurs selon les règles de conversion du langage. Notre template peut donc être instancié. Templates et surcharge des opérateurs La situation devient particulièrement intéressante lorsque le paramètre d’un template représente une classe définie par le programmeur. Considérons deux objets de notre classe Complexe : Complexe z1(3.0, 4.0); Complexe z2(2.0, 1.0); Nous pourrions être tentés d’écrire :
Accueil2026 – Partie II C++ auto z = maximum(z1, z2); Le compilateur peut parfaitement déduire : Mais cette déduction ne suffit pas. Le compilateur doit maintenant vérifier si l’expression : est valide pour deux objets de type Complexe. Contrairement aux nombres réels, les nombres complexes ne possèdent pas d’ordre naturel permettant d’interpréter directement la relation « supérieur à ». Si nous souhaitons néanmoins définir une comparaison pour les besoins de notre programme, nous devons choisir explicitement un critère. Nous pouvons, par exemple, décider de comparer les complexes selon leur module. Comme la fonction racine carrée est croissante pour les valeurs positives, il suffit de comparer les carrés des modules : |z|2 = (Re z)2 + (Im z)2 . Nous pouvons alors surcharger l’opérateur > : bool operator>(const Complexe& autre) const double moduleCarre1 = + imaginaire * imaginaire; double moduleCarre2 = autre.reel * autre.reel + autre.imaginaire * autre.imaginaire; return moduleCarre1 > moduleCarre2; Le template ne connaît pas à l’avance les classes utilisées Notre fonction maximum n’a pas été spécialement conçue pour la classe Complexe. Elle ne contient aucune référence à cette classe. Elle utilise simplement une opération : Lors de l’instanciation, le compilateur vérifie si cette expression est valide avec les types effectivement La généricité du template repose donc moins sur l’identité précise des types que sur les opérations qu’ils permettent. Considérons maintenant :
Accueil2026 – Partie II C++ template <typename T> T additionner(T a, T b) Cette fonction n’exige plus l’existence de l’opérateur >. Elle impose en revanche que l’expression : soit valide pour le type utilisé. Une classe fournissant une surcharge appropriée de operator+ peut donc être utilisée avec ce template. Des exigences exprimées par le code Dans les templates étudiés jusqu’ici, nous n’avons pas écrit explicitement une liste des opérations que doit fournir le type utilisé. Ces exigences apparaissent directement dans le corps du template. Si le template contient : cette expression doit être valide. cette expression doit également être valide. Si une opération nécessaire n’existe pas pour le type utilisé, l’instanciation correspondante ne peut pas être réalisée correctement et la compilation échoue. Complexe(double r, double i) : reel(r), imaginaire(i) bool operator>(const Complexe& autre) const
Accueil2026 – Partie II C++ double moduleCarre1 = + imaginaire * imaginaire; double moduleCarre2 = autre.reel * autre.reel + autre.imaginaire * autre.imaginaire; return moduleCarre1 > moduleCarre2; friend std::ostream& operator<<( template <typename T> return (a > b) ? a : b; Complexe z1(3.0, 4.0); Complexe z2(2.0, 1.0); Éviter les copies inutiles Lorsque les objets manipulés peuvent être volumineux, il peut être préférable de recevoir les arguments par référence constante : template <typename T> T maximum(const T& a, const T& b) return (a > b) ? a : b;
Accueil2026 – Partie II C++ La fonction peut ainsi examiner les objets sans les copier et sans les modifier. Le résultat est encore retourné par valeur dans cette version. Vers les concepts du C++ moderne Les exigences imposées par un template classique sont souvent exprimées implicitement par les opérations présentes dans son corps. Le C++ moderne fournit un mécanisme appelé concept permettant d’exprimer plus explicitement certaines contraintes imposées aux paramètres d’un template. Nous étudierons les concepts ultérieurement. Pour l’instant, nous retiendrons que la validité d’une instanciation dépend non seulement du type déduit, mais également des opérations que ce type permet d’effectuer. • Un template ne fonctionne pas nécessairement avec n’importe quel type. • Les opérations utilisées dans le template imposent des exigences aux types qui lui sont • Si un template contient : les types concernés doivent permettre cette comparaison. • Une classe définie par le programmeur peut satisfaire cette exigence en fournissant, lorsque cela a un sens, une surcharge appropriée de l’opérateur. • La déduction correcte d’un paramètre de type ne garantit pas que l’instanciation correspondante soit utilisable. • Il faut distinguer : déduction du type ̸= validité des opérations effectuées avec ce type • Les templates permettent ainsi d’écrire des algorithmes génériques capables de fonctionner aussi bien avec des types fondamentaux qu’avec des classes définies par le programmeur. • Le C++ moderne permet d’exprimer plus explicitement certaines exigences imposées aux types au moyen des concepts. Templates de classes Jusqu’à présent, nous avons utilisé les templates pour créer des modèles de fonctions. Le même principe peut être appliqué à une classe entière. Une classe template permet de définir une famille de classes dont certains types ne sont pas fixés lors de l’écriture de la classe. Ils seront précisés lors de son utilisation. Une classe liée à un type particulier Considérons la classe suivante :
Accueil2026 – Partie II C++ int getValeur() const Cette classe permet de stocker une valeur de type int. Le type int intervient ici à plusieurs endroits : • dans le type de l’attribut valeur ; • dans le paramètre du constructeur ; • dans le type de retour de getValeur(). Si nous souhaitions construire une classe identique pour des double, nous pourrions naturellement recopier la classe et remplacer chaque int par double. Cette duplication est précisément ce que les templates permettent d’éviter. Transformer la classe en template Nous pouvons remplacer le type particulier int par un paramètre de type : template <typename T> Comme pour une fonction template, la déclaration : template <typename T>
Accueil2026 – Partie II C++ introduit un paramètre de type appelé T. Le paramètre T peut être utilisé dans les différents éléments de la classe exactement comme le serait Il peut notamment déterminer : • le type d’un attribut ; • le type d’un paramètre de fonction membre ; • le type de retour d’une fonction membre. Instanciation d’une classe template Pour créer un objet à partir d’une classe template, nous pouvons préciser le type utilisé comme argument du template : désigne une spécialisation de la classe template Boite pour laquelle : Le compilateur dispose ainsi de toutes les informations nécessaires pour former le type correspondant. Le même template peut être utilisé avec un autre type : Boite<double> b2(3.14); Le membre valeur, le paramètre du constructeur et le type de retour de getValeur() utilisent donc tous le type double. Boite<double> b2(3.14); Les objets b1 et b2 ne sont pas simplement deux objets de même type contenant des données différentes. Leurs types sont respectivement : Ces deux types sont distincts.
Accueil2026 – Partie II C++ Template et occupation mémoire Le paramètre de type d’un template est utilisé par le compilateur pour construire les types nécessaires au programme. Il n’est pas nécessaire de stocker dans chaque objet une information supplémentaire indiquant le type utilisé pour remplacer T. template <typename T> contient simplement son membre de type int, sous réserve des règles habituelles d’alignement et de représentation des objets. L’utilisation de plusieurs instanciations d’un template peut en revanche augmenter la taille du code compilé, puisque différentes versions de certaines fonctions peuvent être générées pour différents Il faut donc distinguer : • la mémoire occupée par les objets pendant l’exécution ; • la taille du code correspondant aux différentes instanciations du template. Cette distinction prendra une importance particulière dans les systèmes embarqués, où la mémoire contenant le programme est limitée. Une famille de classes Un seul template peut permettre au compilateur de former plusieurs types : Boite<double> b2(3.14); Boite<char> b3(’A’); Nous obtenons ainsi trois types distincts : Une classe template peut donc être considérée comme un modèle permettant de définir une famille Utiliser une classe comme argument de type Le paramètre d’un template n’est pas limité aux types fondamentaux. Une classe définie par le programmeur peut également être utilisée : Complexe z(3.0, 4.0); Boite<Complexe> b(z);
Accueil2026 – Partie II C++ Dans cette instanciation : Le membre valeur est donc lui-même un objet de type Complexe. Les exigences imposées au type restent présentes Comme pour les fonctions templates, les opérations utilisées dans une classe template imposent des exigences au type choisi. void afficher() const std::cout << valeur << std::endl; le type T doit permettre l’utilisation de l’expression : Pour une classe définie par le programmeur, cela peut notamment nécessiter une surcharge appropriée de l’opérateur <<. Vers une classe Matrice Le même mécanisme pourra être utilisé pour construire une classe Matrice indépendante du type particulier de ses coefficients. Nous pourrons ainsi écrire : template <typename T> std::size_t colonnes; pourra alors devenir, selon l’instanciation : Nous pourrons ainsi créer, à partir d’un même modèle de classe :
Accueil2026 – Partie II C++ Nous construirons cette classe dans un chapitre ultérieur. Code complet de la classe Boite : template <typename T> Boite<double> b2(3.14); Boite<char> b3(’A’);
Accueil2026 – Partie II C++ • Un template peut être appliqué à une classe entière. • Une classe template est introduite par une déclaration telle que : template <typename T> • Le paramètre de type T peut être utilisé pour définir les attributs, les paramètres et les types de retour des fonctions membres. • Une instanciation telle que : utilise int comme argument du paramètre de type T. • Deux instanciations telles que : constituent deux types distincts. • Une classe template peut donc être considérée comme un modèle permettant de construire une famille de types. • Le paramètre de type n’est pas stocké comme une information supplémentaire dans chaque objet. Il est exploité lors de la compilation. • Plusieurs instanciations peuvent en revanche augmenter la taille du code compilé. • Un paramètre de type peut représenter aussi bien un type fondamental qu’une classe définie par le programmeur. • Comme pour les fonctions templates, les opérations utilisées dans la classe imposent des exigences aux types avec lesquels elle est instanciée. Plusieurs paramètres de type dans une classe Une classe template peut utiliser plusieurs paramètres de type. Nous avons jusqu’ici écrit : template <typename T> mais rien n’impose de se limiter à un seul paramètre. Nous pouvons par exemple écrire : template <typename T, typename U> Les paramètres de type T et U sont alors indépendants.
Accueil2026 – Partie II C++ Deux types indépendants template <typename T, typename U> T getPremier() const Le membre premier possède le type représenté par T. Le membre second possède le type représenté par U. Les paramètres de type étant indépendants, rien n’impose qu’ils représentent des types différents. Paire<double, double> p(2.5, 7.8); est parfaitement valide. Chaque combinaison constitue un type distinct Les instanciations : constituent des types différents. Le compilateur forme donc autant de types concrets que nécessaire à partir du même modèle de
Accueil2026 – Partie II C++ Fig. 8.5 – Schéma. Les paramètres de type peuvent naturellement représenter des classes définies par le programmeur. Paire<Complexe, int> p(z, 3); est possible si les opérations utilisées par la classe Paire sont compatibles avec le type Complexe. • Une classe template peut posséder plusieurs paramètres de type. • On peut écrire : template <typename T, typename U> pour déclarer deux paramètres de type indépendants. • Les paramètres peuvent représenter des types différents ou identiques.
Codes et programmes contenus dans le chapitre
Paire
Classe template Paire
Boite
Classe template Boite
README.pdf
Explications sur l’utilisation des codes