La généricité permet d'avoir des fonctions et des classes paramétrables, c'est-à-dire que, au moment où nous en avons besoin, nous précisons le type à utiliser pour ladite fonction ou ladite classe. C'est le type qui est paramétrable. Ce concept nous permettra d'avoir des écritures plus concises et ainsi d'éviter de nombreuses surdéfinitions. La généricité est souvent appelée « template » ( patron – évocation de la haute couture), ou également « modèle ».
Prenons l'exemple de la fonction qui retourne la valeur minimum de deux nombres.
La difficulté, dans ce genre de situation, est de proposer les bons types à la fois pour les paramètres et pour la valeur de retour. A priori, il semble que se soit le type réel qui soit le plus adapté. En effet, le compilateur accepte les écritures prévues pour « y » et pour « z ». En prenant le type le plus haut dans la hiérarchie, nous sommes sûrs que le compilateur acceptera des écritures proposant des types dits « plus petit » puisque le langage effectue le changement de type automatique. De plus, en prenant le type le plus fort, nous n'obtenons aucune perte d'informations.
Dans le premier cas, l'utilisation est parfaitement adaptée puisque les arguments placés lors de l'appel de la fonction sont bien de type réel. Le résultat de cette fonction est compatible avec la variable « y » qui est elle-même de type réel.
Dans le deuxième cas, l'adéquation n'est pas parfaite puisque les arguments sont des entiers. Au moment de l'exécution, nous aurons des changements de type pour passer des types entiers vers les types réels. De même, la fonction renvoie un réel qui, lui aussi doit être transformer pour s'adapter à la variable « z » qui est de type entier.
Ce code fonctionne bien, mais il n'est pas très optimisé puisque nous constatons une perte de temps au moment des changements de type automatiques. Il serait alors souhaitable de proposer des fonctions qui soient adaptées à chaque situation particulière. Puisque le langage le permet, nous pouvons surdéfinir la fonction « min » en proposant une fonction spéciale pour les réels et une fonction spéciale pour les entiers.
Cette fois-ci, chaque fonction est adaptée à la situation proposée. En effet, « y » appelle la première fonction alors que « z » appelle la deuxième fonction. Si nous regardons bien, nous découvrons que le code est identique, c'est juste la signature qui diffère.
Dans le même ordre d'idée, nous pourrions écrire des fonctions pour des « long », des « long double », etc. Pour éviter tous ces cas de figure, et vu que le code interne est identique, il serait peut-être plus judicieux de proposer une fonction qui soit un modèle pour toutes les autres.

Que se passe t-il réellement lorsque nous utilisons les modèles ?

Il faut bien comprendre que la transformation du modèle vers de véritables fonctions, s'effectue durant la phase de compilation, et dans le scénario proposé, le compilateur fabrique bien deux fonctions différentes. Lorsque nous lancerons le programme, c'est bien ces deux fonctions qui seront directement appelées, le modèle, à ce moment là, n'existe plus. Le modèle nous sert en fait à composer, une fois pour toute, les lignes de codes nécessaires à l'élaboration de fonctions surdéfinies et c'est le compilateur qui finalement travaille pour nous. Cela nous évite d'écrire des lignes de codes identiques.
Remarque : Encore une fois, nous sommes en présence de l'élaboration d'un texte (paramétrable) par rapport à un autre et pour être plus précis, cette mise en place du texte s'effectue durant la phase du préprocesseur ce qui sous-entend que les fonctions génériques ainsi que les classes génériques devront se trouver dans un fichier en-tête.
Nous pouvons modéliser n'importe quel type de fonction et le fait qu'elle puisse être « inline » ne change absolument rien. Dans le cas de la fonction « min », il serait d'ailleurs judicieux de la qualifier « inline » puisque le code est extrêmement réduit.

L'avantage de ces modèles c'est d'écrire très peu de ligne de codes. De plus, le modèle proposé pour trouver une valeur minimale répond à des situations très différentes. Il existe toutefois, des cas où le modèle proposé ne suffit plus. Nous pouvons avoir besoin, par exemple, de récupérer la valeur minimale d'un tableau d'entiers passé en argument, auquel cas, la définition proposée par le modèle n'est plus du tout adaptée à la situation. Il est alors nécessaire de proposer une définition particulière à ce cas d'utilisation.
Nous pouvons donc faire coexister des fonctions génériques avec des fonctions classiques. Le tout, c'est de proposer des signatures différentes pour que le compilateur soit à même de comprendre le souhait du programmeur et donc de résoudre la surdéfinition.

Il est même possible de surcharger des fonctions génériques entre elles. Nous avons pris l'exemple d'un tableau d'entier, mais nous imaginons bien que le codage proposer puisse fonctionner tout aussi bien pour un tableau de réels, de caractères, etc. Il vient donc à l'esprit que nous pouvons finalement en faire une fonction générique plutôt que de figer le codage pour un seul cas particulier qui traite uniquement des entiers.

Avec très peu de lignes de code, nous gérons déjà une très grande diversité de situations.
Les classes aussi peuvent être génériques. Dans la leçon précédente, nous avons mis en œuvre une classe représentant les tableaux d'entiers. Cette classe nous a permis de créer un véritable tableau qui tenait compte à la fois de l'affectation directe de tout le contenu et également d'un accès particulier à l'une de ses cases. Malgré tout, l'effort que nous avons fourni n'est pas suffisamment récompenser puisque ce tableau est codé seulement pour les entiers.

Lorsque nous consultons le code interne, nous remarquons que nous avons très peu de référence au type entier, et surtout, nous pouvons le remplacer par un autre type pour retrouver un fonctionnement identique pour d'autres types de tableau. Dans ce cas, il est plus avantageux de proposer un tableau générique, et c'est l'utilisateur qui décidera le type qu'il désire.

La syntaxe demeure identique à l'écriture des fonctions génériques. Partout où le type « int » est utilisé, vous le remplacez par le paramètre « Type » puisque c'est lui qui est défini dans le modèle.
Mise à part l'écriture de la partie paramétrable, les classes génériques demandent très peu d'investissement supplémentaire. Il ne faut pas hésiter à utiliser cette technique.

Il existe toutefois une petite différence dans l'utilisation des classes génériques par rapport aux fonctions génériques.
Le compilateur, au moment de l'appel d'une fonction générique, contrôle la signature proposée et détermine le type demandé pour effectivement fabriquer la fonction avec le type désiré. Sans spécification supplémentaire, le compilateur arrive à connaître le type demandé.
Dans le cas d'un objet, c'est différent. Lorsque nous déclarons cet objet, le type de certains attributs n'apparaît pas au moment de la déclaration puisque seul le nom de l'objet est visible. En conséquence, il est nécessaire, durant la création d'indiquer le ou les types voulus. La syntaxe est simple d'utilisation. Après le nom de la classe, vous devez préciser le type voulu entre « <> ». Le nom de votre type devient alors l'argument de votre classe générique. Du coup, la syntaxe de votre code est très lisible, en ce sens que nous voyons bien qu'il s'agit, par exemple, d'un tableau d'entiers ou d'un tableau de complexes.
Remarque : L'utilisation des « < > » est impérative dans le cas des classes génériques au même titre que l'utilisation des parenthèses est impérative pour les fonctions et les méthodes. C'est ce qui permet d'ailleurs de les reconnaître.

D'habitude, lorsque nous développons des classes, nous plaçons la déclaration de la classe dans un fichier en-tête alors que la définition de ses méthodes se trouve dans le fichier source correspondant qui porte le même nom, mais dont l'extension de fichier est « *.cpp ».
Dans le cas d'une classe générique, c'est différent. N'oubliez pas qu'il s'agit d'un prototype (d'un patron) qui servira à la fabrication (réelle) de plusieurs classes de types différents. Dans ce contexte, la déclaration de la classe ainsi que la définition des méthodes doivent se trouver entièrement dans le fichier en-tête.
En effet, tout ce qui est générique est utilisé uniquement par le « préprocesseur » du compilateur, et n'oubliez pas que cette phase particulière de la compilation propose de transformer un texte par un autre. Ce n'est que lorsque le texte a été mis en place que la compilation réelle s'effectue.
En revenant sur notre exemple, à l'utilisation, nous avons besoin d'avoir d'une part un tableau d'entiers et d'autre par un tableau de réels. Bien que le code interne soit identique, il existe tout de même des différences. Nous ne traitons pas des réels comme des entiers. Il faut donc qu'il y ait, par exemple, un constructeur pour un tableau d'entier et un constructeur pour un tableau de réel puisque la réservation mémoire dynamique est totalement différente. Les réels prennent plus de place en mémoire. Pour un tableau de complexe, c'est encore pire.

Du coup, la généricité de la définition des méthodes doit être pris en compte au même titre que la généricité de la déclaration de la classe dans son entier. En effet, de même que nous devons qualifier la méthode par le nom de la classe à laquelle elle appartient. De même, nous devons spécifier qu'il s'agit bien d'une méthode générique qui va manipuler des types différents. Finalement, les méthodes sont également paramétrées. Dans ce contexte, à la définition de chaque méthode, vous devez impérativement utiliser la syntaxe complète des « templates ».
Nous avons vu qu'il existait deux types de paramètres pour les fonctions et les classes génériques : les paramètres de type, et les paramètres non type.

Dans le cas des classes génériques, le paramètre « non type » peut s'avérer particulièrement utile, notamment pour implémenter un tableau de type quelconque en spécifiant, dès le départ, la dimension du tableau, et c'est ce dernier élément qui servira de paramètre non type. En effet, dans ce cas là, le paramètre attend une valeur entière non signée. Il ne s'agit en aucun cas de proposer un type mais bien une valeur.

En prenant un paramètre non type pour spécifier la dimension, le code de la classe devient extrêmement simple. En effet, comme la dimension est connue en moment de la création de la classe, il est possible de mettre comme attribut un tableau et non plus un pointeur vers une variable dynamique. Du coup, nous n'avons plus besoin de redéfinir tous les comportements par défaut. Il suffit de redéfinir l'opérateur de crochets « [ ] ».
