Template ✱ ********** .. include:: ../BoutonGoogleTrad.rst Modèle de fonction ================== Introduction ------------ Prenons une fonction *max()* qui traite des entiers : .. code-block:: int max(int a, int b) { if (b>a) return b; else return a; } Supposons que l'on appelle cette *fonction max()* avec pour arguments deux valeurs flottantes. Que se passe-il ? Pour initialiser les paramètres, on convertit ces valeurs en entier. Mais surtout, après son traitement, la fonction va retourner un entier ce qui peut poser problème. En effet, lorsqu'on appelle *max(3.2,4.5)*, on ne s'attend pas à récupérer comme résultat 4. Pour corriger ce problème, une solution consiste à utiliser le polymorphisme ad hoc en écravant une fonction pour chaque type concerné : .. code-block:: int max(int a, int b) { if (b>a) return b; else return a; } float max(float a, float b) { if (b>a) return b; else return a; } double max(double a, double b) { if (b>a) return b; else return a; } Cependant, ce choix d’implémentation oblige à répéter plusieurs fois maladroitement la fonction de départ, ceci pour chaque type à traiter. C’est ainsi qu’est apparu un nouveau concept : la **généricité**, c’est-à-dire la capacité d’une fonction à paramétrer ses types. Un tel code s’appelle un modèle ou un patron (**template**). L’idée est simple : de la même manière qu’on peut transmettre des valeurs comme arguments d’une fonction, pourquoi ne pas transmettre aussi des types en arguments ? Voici un exemple de définition d’un template de la fonction *max()* ainsi que son appel : : .. code-block:: #include template T max(T a, T b) { if (b>a) return b; else return a; } int main() { std::cout << max(1,2) << std::endl; std::cout << max(1.3,4.5) << std::endl; } Entier ou objet ? ----------------- En choisissant d'écrire une fonction en utilisant un template, vous allez finir à un moment par vous mélanger. En effet, pour un argument en lecture seule, vous avez l'habitude de passer les types fondamentaux (int, double...) par valeur et les objets par const référence, ce qui est la bonne attitude. Cependant, dans le cas d'un template, on ne sait pas ce que représente le type *T* : un entier ou un objet bigInteger. Par conséquent, on prend la convention suivante qui a le mérite de fonctionner dans les deux configurations : .. panels:: :column: col-lg-10 p-2 **CONVENTION** : Dans une fonction template, les paramètres en lecture seule sont passés en utilisant des **const reference**. En ce qui concerne les arguments devant être modifiés, la question ne se pose pas : que ce soit pour les types fondamentaux ou les objets, le passage se fait dans tous les cas par référence, donc aucune ambigüité cette fois. En résumé, voici la syntaxe optimale de la fonction template *max()* pouvaint ainsi aussi bien traiter des *int* que des *bigInteger* : .. code-block:: template T max(const T & a, const T & b) { T result; ... return result; } Règles de déduction =================== Soit vous transmettez lors de l'appel de la fonction template tous les paramètres, soit vous demandez au compilateur de les déduire de manière implicite. Dans le cas des template, il n'y pas de place à l'ambigüité, .. panels:: :column: col-lg-10 p-2 **CONVENTION** : Dans une fonction template, la déduction du type d'un paramètre T doit être univoque et identique pour tous les arguments. Exemple 1 : appel implicite --------------------------- Si nous utilisons le code suivant, nous obtenons une erreur : .. code-block:: #include template T mean(const T & a, const T & b, const T & c) { return (a+b+c)/3; } int main() { std::cout << mean(1,2,3.14) << std::endl; } >> no matching function for call to ‘mean(int, int, double)’ Le compilateur n'a pas trouvé de fonction/fonction template acceptant un (int,int,double). Si la fonction *mean()* aurait pu été retenue si on avait passé comme arguments (int,int,int) ou (double,double,double). Il n'y a pas à ce niveau de mécanisme de conversion implicite. Cependant, si l'on transmet explicitement les types au template, alors les mécanismes de conversion implicite sont actifs : Ainsi, il faut indiquer explicitement le type utilisé par la fonction template : .. code-block:: template T mean(const T & a, const T & b, const T & c) { return (a+b+c)/3; } int main() { std::cout << mean(1,2,3.14) << std::endl; } >> 2 Exemple 2 : le tout générique ----------------------------- Techniquement, il est possible d'utiliser plusieurs typename dans un template. Ainsi, on peut pousser le concept encore plus loin en paramétrant tous les types utilisés, il est même possible de paramétrer le type de retour séparement ! .. code-block:: #include template ReturnType mean(const T1 & a, const T2 & b, const T3 & c) { return (ReturnType)((a+b+c)/3); } int main() { std::cout << mean(9.7,4.8,5) << std::endl; } >> 6.5 Si plusieurs paramètres templates existent, la norme du langage permet de n'en instancier qu'une partie. Ainsi, dans le contexte de l'exemple précédent, on peut écrire : *mean(...)*, les deux paramètres suivant étant alors géré comme des paramètres implicites. Quizzz ====== Indiquez si les syntaxes proposées sont valides (V) ou fausses (F) : .. quiz:: Template :title: Fonction template * :quiz:`{"type":"TF","answer":"T"}` template T max(T a, T b) {...} * :quiz:`{"type":"TF","answer":"F"}` template T max(U a, U b) {...} * :quiz:`{"type":"TF","answer":"T"}` template A max(A a, B b) {...} * :quiz:`{"type":"TF","answer":"F"}` template A max(T a, T b) {...} * :quiz:`{"type":"TF","answer":"T"}` template A max(B a, C b) {...} et avec la fonction : .. code-block:: template int mx(A a) { if (a>B) return a; else return 0;} * :quiz:`{"type":"TF","answer":"F"}` mx(7); * :quiz:`{"type":"TF","answer":"T"}` mx<4,int>(9.3); * :quiz:`{"type":"TF","answer":"F"}` mx(7); * :quiz:`{"type":"TF","answer":"F"}` mx<5>(7,9); * :quiz:`{"type":"TF","answer":"T"}` mx<5>(3.14); Template class ============== Syntaxe ------- Les template class fournissent un mécanisme élégant pour créer des classes génériques opérant sur divers types de données. Leur syntaxe est similaire au modèle de fonction. Voici un exemple : .. code-block:: template struct tuple { T a; T b; }; int main() { tuple t; t.a = 5; t.b = 1; } Notes diverses ============== Assistance de Visual Studio --------------------------- En faisant un détour par Visual Studio, si vous placez le curseur de la souris sur l'appel d'une fonction générique, l'interface vous indique la version choisie par le compilateur : .. image:: visual.png :align: center :scale: 40% Opérateur de conversion de type ------------------------------- Maintenant que nous connaissons la syntaxe des fonctions templates, nous pouvons introduire l'opérateur de convertion de type : **static_cast**. En donnant le type attentdu, on peut convertir tout élément vers le type désiré si l'opération est licite : .. code-block:: int main() { double pi = 3.1415; std::cout << static_cast(pi) << std::endl; } >> 3 Fonction template avec littéral ------------------------------- Les paramètres des fonctions templates ne se limitent pas aux types, on peut aussi fournir des littéraux ! Cela peut apporter un gain de performance notable car on injecte des constantes dans le code durant la phase de compilation. Voici un exemple : .. code-block:: cpp template T multiply(T value) { return value * N; } int main() { std::cout << multiply(10) << std::endl; // Multiplies 10 by 5 std::cout << multiply(2.5) << std::endl; // Multiplies 2.5 by 3 return 0; } Classe template avec littéral ----------------------------- Les templates de classe peuvent aussi utiliser des literraux. Dans l'exemple ci dessous, plûtot que créer un vecteur de taille quelconque, on force dès la compilation la taille de ce container, ce qui garantit une exécution optimale : .. code-block:: #include template struct vfixed { T v[size]; void Reset(T r) { for (int i = 0; i < size; i++) v[i] = r + i; } }; void main() { vfixed v20; v20.Reset(10); std::cout << v20.v[0] << " " << v20.v[19] << std::endl; } >> 10 29 Nommer des templates spécialisés -------------------------------- Si vous trouvez la syntaxe à base de <> trop longue, rien ne vous empêche d'instancier un template et de lui associer un nom : .. code-block:: #include template struct vfixed { T v[size]; void Reset(T r) { for (int i = 0; i < size; i++) v[i] = r + i; } }; using v20i = vfixed; void main() { v20i v20; v20.Reset(10); std::cout << v20.v[0] << " " << v20.v[19] << std::endl; } >> 10 29 Quizzz ====== .. quiz:: Template :title: Questions * :quiz:`{"type":"TF","answer":"F"}` Les arguments des templates peuvent être des variables. * :quiz:`{"type":"TF","answer":"T"}` Je peux utiliser le paramètre template d'une classe comme argument d'une fonction template. * :quiz:`{"type":"TF","answer":"T"}` La place d'un template est dans un fichier header. * :quiz:`{"type":"TF","answer":"F"}` Comme argument d'un template, on ne peut trouver que des types. * :quiz:`{"type":"TF","answer":"T"}` On peut donner un nom à une classe template **spécialisée** (template avec template arguments) * :quiz:`{"type":"TF","answer":"T"}` Les arguments des templates doivent être connus à la compilation. Travail à rendre sur Github Classroom ===================================== Exercice 1 : template function ------------------------------ * Créez un fichier nommé *5_template_fnt.cpp* * Dans le code exemple ci-dessous, transformez les 4 fonctions données vers leurs versions templates. * Après cela, écrivez une version template de la fonction *test()* pour qu'elle teste les 4 fonctions dans le type donné en argument. * Changez le *main()* pour que les tests soient effectués en entier, en double et en flottant. Les valeurs numériques restent les mêmes dans la fonction *test()*. * Uploadez le fichier sur votre github .. code-block:: #include using namespace std; int max(int a, int b) { if (a