Template ******** .. include:: ../BoutonGoogleTrad.rst Modèle de fonction ================== On utilise :cppterm:`using namespace std;` pour limiter la présence des :cpp:`std::`. Introduction ------------ La fonction :cpp:`maxi()` ci-dessous ne travaille qu’avec des entiers : .. code-block:: int maxi(int a, int b) { if (b>a) return b; else return a; } Si on utilise des nombres à virgule comme arguments, comme par exemple en écrivant :cpp:`maxi(3.2, 4.5)`, ils seront alors transformés en nombres entiers. La fonction renverra donc 4 au lieu de 4.5. Pour éviter ce problème, on peut créer plusieurs versions de la fonction :cpp:`maxi()`, une pour chaque type :cpp:`(int, float, double, etc.)`. .. code-block:: int maxi(int a, int b) { if (b>a) return b; else return a; } float maxi(float a, float b) { if (b>a) return b; else return a; } double maxi(double a, double b) { if (b>a) return b; else return a; } Cela peut être très long, et il faut le faire pour chaque fonction. Pour éviter cela, on utilise la généricité (:cppterm:`genericity`), un mécanisme de programmation qui permet d’écrire un patron de fonction capable de fonctionner avec plusieurs types. Cette technique s’appuie sur le mot-clé :cppterm:`template` pour désigner :cpp:`T` comme un type générique. Ainsi, dans la fonction qui suit ce mot-clef, on peut utiliser le type :cpp:`T` comme tout autre type. Voici un exemple : .. code-block:: #include using namespace std; template T maxi(T a, T b) { if (b>a) return b; else return a; } int main() { cout << maxi(1,2) << endl; cout << maxi(1.3,4.5) << endl; } Référence ou copie ? -------------------- Lorsque vous écrivez une fonction, vous pouvez choisir un passage par copie ou par référence. Normalement, la stratégie est la suivante : - Pour des types fondamentaux, on préfère un passage par copie car plus efficace - Pour les objets, on préfère un passage par :cpp:`reference` / :cpp:`const reference` pour éviter une copie Cependant, pour les patrons de fonction, nous ne savons pas le type utilisé à l'avance, et les autres programmeurs peuvent aussi bien utiliser votre template en passant des types fondamentaux ou des objets. Comme le surcoût d'une copie d'un gros objet est généralement plus important que le surcoût d'un passage par référence sur un type fondamental, on préfère adopter la convention suivante : .. panels:: :column: col-lg-10 p-2 Si votre fonction template peut manipuler des objets, préférez un passage par :cpp:`reference` ou :cpp:`const reference`. Si elle ne manipule que des types fondamentaux, un passage par valeur convient généralement très bien. Pour le type de retour, les règles habituelles s'appliquent : objet pour un temporaire/local, référence pour un élément pérenne. Exemple ------- Prenons une fonction qui : - lit deux objets en entrée → 2 paramètres *const reference* - retourne un nouvel objet → retour par valeur Voici le code correspondant : .. code-block:: template T exemple(const T & a, const T & b) { T result; ... return result; } Templates à plusieurs paramètres -------------------------------- Techniquement, il est possible d'utiliser plusieurs *typename* dans un :cpp:`template`. Voici un exemple : .. code-block:: template struct Element { Key key; Value value; }; int main() { Element player; Element price; } Fonction template avec valeur constante --------------------------------------- Les paramètres des fonctions templates ne se limitent pas aux types, on peut aussi leur fournir des valeurs constantes ! Voici un exemple avec la fonction puissance, le paramètre :cpp:`N` défini dans le template est utilisé directement dans le code comme une valeur : .. code-block:: cpp template double power(double x) { double result = 1; for (int i = 0; i < N; ++i) result *= x; return result; } int main() { cout << power<7>(2.0) << endl; // 128 } Ainsi, la valeur 7 est connue à la compilation et peut être exploitée par le compilateur pour optimiser le code. Appel d'un template =================== Spécification explicite ----------------------- L'appel explicite consiste à spécifier le type :cpp:`T` lors de l'appel de la fonction template : .. code-block:: int result = maxi(4.3,2); // result : 4 Cette syntaxe permet d'instancier la fonction template (:cppterm:`instantiate the function template`) *maxi(int a, int b)* à partir du patron de fonction :cpp:`T maxi(T a,T b)`. Les arguments sont alors convertis vers le type imposé, c'est à dire le type :cpp:`int`. Ainsi, cet appel retourne 4. Déduction implicite ------------------- Il est également possible de ne pas préciser le type :cpp:`T`. Celui-ci est alors déduit automatiquement à partir du type des arguments. .. panels:: :column: col-lg-10 p-2 **Règle** : Lors d’une déduction implicite, toutes les occurrences du paramètre :cpp:`T` doivent conduire à la même déduction de type. Ainsi : .. code-block:: maxi(2, 3); // T = int maxi(2.0, 3.0); // T = double maxi(2, 3.0); // error : T = int ? double ? Quizzz ====== .. quiz:: Template :title: Fonction template Indiquez si les syntaxes proposées sont valides (V) ou fausses (F) : * :quiz:`{"type":"TF","answer":"T"}` :cpp:`template T maxi(T a, T b) {...}` * :quiz:`{"type":"TF","answer":"F"}` :cpp:`template T maxi(U a, U b) {...}` * :quiz:`{"type":"TF","answer":"T"}` :cpp:`template A maxi(A a, B b) {...}` * :quiz:`{"type":"TF","answer":"F"}` :cpp:`template A maxi(T a, T b) {...}` * :quiz:`{"type":"TF","answer":"T"}` :cpp:`template A maxi(B a, C b) {...}` Indiquez si les appels suivants sont corrects (V) ou produisent une erreur (F) : .. code-block:: template int mx(A a) { if (a>B) return a; else return 0; } * :quiz:`{"type":"TF","answer":"F"}` :cpp:`mx(7);` * :quiz:`{"type":"TF","answer":"T"}` :cpp:`mx<4,int>(9);` * :quiz:`{"type":"TF","answer":"T"}` :cpp:`mx<2,int>(4.3);` * :quiz:`{"type":"TF","answer":"F"}` :cpp:`mx(7);` Classe template =============== Syntaxe ------- Les classes :cpp:`template` 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 template de fonction. Voici un exemple : .. code-block:: template struct vec2D { T a; T b; }; int main() { vec2D t; t.a = 5; t.b = 1; } Fonctions membres d’une classe template --------------------------------------- Lorsque vous écrivez une fonction membre dans une classe template, il est possible de réutiliser le paramètre template :cpp:`T` de la classe dans cette fonction : .. code-block:: template struct vec2D { T a; T b; void set(T v) { a = b = v; } T getSum() { T r = a+b; return r; } }; Classe template avec constante ------------------------------ Les templates de classe peuvent aussi utiliser des constantes. Voici un exemple avec un vecteur de taille fixe, mais paramétrable à la compilation : .. code-block:: #include template struct FixedVector { T v[Size]; void reset(T r) { for (int i = 0; i < size; i++) v[i] = r + i; } }; int main() { FixedVector v20; v20.reset(10); cout << v20.v[0] << " " << v20.v[19] << endl; } >> 10 29 Nommer des classes templates spécialisées ----------------------------------------- Si vous trouvez l'écriture :cpp:`FixedVector` trop longue, vous pouvez instancier ce template et lui associer un nom. Ainsi, on peut utiliser l'exemple pour écrire : .. code-block:: using vi20 = FixedVector; int main() { vi20 v; v.reset(10); } Quizzz ====== .. quiz:: Template :title: Questions * :quiz:`{"type":"TF","answer":"T"}` Les arguments des templates peuvent être des valeurs constantes. * :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":"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 * :quiz:`{"type":"TF","answer":"T"}` Les arguments des templates doivent être connus à la compilation. Travail à rendre sur l'espace partagé ===================================== Exercice 1 : template function ------------------------------ * Créez un fichier nommé :cpp:`template_fnt.cpp` * Dans le code exemple ci-dessous, transformez les 4 fonctions en fonctions templates. * Modifiez ensuite la fonction :cpp:`test()` afin qu’elle teste ces quatre fonctions templates. * Transformez également :cpp:`test()` en fonction template, afin de pouvoir choisir le type utilisé pour les tests. * Enfin, dans la fonction :cpp:`main()`, appelez la fonction :cpp:`test()` pour des types :cpp:`int` et :cpp:`double`. Les valeurs passées devront correspondre au type considéré. .. code-block:: #include using namespace std; int maxx(int a, int b) { if (a>b) return a; else return b; } void swap_int(int& a, int& b) { int tmp = a; a = b; b = tmp; } void leftRotate(int& a, int& b, int& c) { int tmp = a; a = b; b = c; c = tmp; } int& inc(int& a) { ++a; return a; } void test(int a, int b, int c) { cout << "a=" << a << ", b=" << b << ", c=" << c << endl; cout << "maxx(" << a << "," << b << ") = " << maxx(a, b) << endl; cout << "maxx(" << b << "," << c << ") = " << maxx(b, c) << endl; swap_int(a, b); cout << "swap: a=" << a << ", b=" << b << endl; leftRotate(a, b, c); cout << "leftRotate: a=" << a << ", b=" << b << ", c=" << c << endl; inc(c); cout << "inc(c): c=" << c << endl; inc(inc(c)); cout << "inc(inc(c)): c=" << c << '\n'; } int main() { test(-1,5,10); } * Déposez votre fichier dans l'espace partagé Exercice 2 ---------- Dans le chapitre précédent, nous avons codé la classe :cpp:`V10` correspondant à un vecteur de 10 entiers. Nous allons maintenant mettre en place sa version template. * Créez un fichier nommé :cpp:`template_V10.cpp`. * Transformez la classe :cpp:`V10` créée dans le chapitre précédent afin de la rendre générique grâce aux templates C++. * Votre nouvelle classe devra permettre de choisir à la fois le type des données et la taille du tableau. * Par exemple : .. code-block:: cpp V10 a; V10 b; V10 c; * La classe devra reprendre les fonctionnalités de :cpp:`V10` : constructeurs, :cpp:`size(), get(), set(), print(), equals(), operator[], find(), copyFrom() et operator=` * Veillez à utiliser le mot-clé :cpp:`const` partout où cela est pertinent, aussi bien pour les méthodes que pour les paramètres. * Pour vérifier votre code, vous devez utiliser les tests suivants : :download:`tests.txt ` * Déposez votre fichier contenant la classe et les tests dans l'espace partagé .. warning:: :cppterm:`IA or not IA, that is the question.` Ces exercices sont au programme de l'examen.