Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
fonctions_generiques [2016/10/18 02:55] gbdivers |
fonctions_generiques [2016/12/19 01:11] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ^ [[surcharge_fonctions|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[fonctions_lambdas|Chapitre suivant]] ^ | + | ^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^ |
- | ====== Fonctions génériques ====== | + | |
+ | ====== Les fonctions génériques ====== | ||
===== Intérêt de la programmation générique ===== | ===== Intérêt de la programmation générique ===== | ||
Ligne 28: | Ligne 29: | ||
</code> | </code> | ||
- | Rien de compliqué, c'est une fonction classique qui prend deux parametres entiers et retourne un entier. | + | Rien de compliqué, c'est une fonction classique qui prend deux paramètres entiers et retourne un entier. |
Si vous ajoutez le second calcul suivant : | Si vous ajoutez le second calcul suivant : | ||
Ligne 41: | Ligne 42: | ||
int main() { | int main() { | ||
std::cout << add(3, 4) << std::endl; | std::cout << add(3, 4) << std::endl; | ||
- | std::cout << add(1.2, 3.4) << std::endl; | + | std::cout << add(1.7, 3.8) << std::endl; |
} | } | ||
</code> | </code> | ||
Ligne 49: | Ligne 50: | ||
<code> | <code> | ||
main.cpp:9:22: warning: implicit conversion from 'double' to 'int' | main.cpp:9:22: warning: implicit conversion from 'double' to 'int' | ||
- | changes value from 1.2 to 1 [-Wliteral-conversion] | + | changes value from 1.7 to 1 [-Wliteral-conversion] |
- | std::cout << add(1.2, 3.4) << std::endl; | + | std::cout << add(1.7, 3.8) << std::endl; |
~~~ ^~~ | ~~~ ^~~ | ||
main.cpp:9:27: warning: implicit conversion from 'double' to 'int' | main.cpp:9:27: warning: implicit conversion from 'double' to 'int' | ||
- | changes value from 3.4 to 3 [-Wliteral-conversion] | + | changes value from 3.8 to 3 [-Wliteral-conversion] |
- | std::cout << add(1.2, 3.4) << std::endl; | + | std::cout << add(1.7, 3.8) << std::endl; |
~~~ ^~~ | ~~~ ^~~ | ||
2 warnings generated. | 2 warnings generated. | ||
Ligne 61: | Ligne 62: | ||
</code> | </code> | ||
- | Premièrement, le code émet deux avertissements, du fait de la conversion implicite de ''double'' en ''int''. Et deuxièmement, le résultat obtenu n'est pas correct, la valeur attendue (4.6) est arrondie. La raison est que vous avez écrit une fonction ''add'' qui utilise des entiers comme parametres. Le compilateur a le choix entre réaliser une conversion des types si c'est possible, ou de produire une erreur si ce n'est pas le cas. | + | Premièrement, le code émet deux avertissements, du fait de la conversion implicite de ''double'' en ''int''. Et deuxièmement, le résultat obtenu n'est pas correct, la valeur attendue (5.5) est arrondie à la valeur 4. |
+ | |||
+ | La raison est que vous avez écrit une fonction ''add'' qui utilise des entiers comme paramètres. Le compilateur a le choix entre réaliser une conversion des types si c'est possible, ou de produire une erreur si ce n'est pas le cas. Dans le cas present, il choisit la conversion implicite, avec un avertissement. | ||
L'appel de la fonction ''add'' avec des valeurs de type ''double'' est équivalent au code suivant : | L'appel de la fonction ''add'' avec des valeurs de type ''double'' est équivalent au code suivant : | ||
<code cpp> | <code cpp> | ||
- | const int lhs = 1.2; // arrondi en 1 | + | const int lhs = 1.7; // arrondi en 1 |
- | const int rhs = 3.4; // arrondi en 3 | + | const int rhs = 3.8; // arrondi en 3 |
add(lhs, rhs); // calcul 1 + 3 | add(lhs, rhs); // calcul 1 + 3 | ||
+ | </code> | ||
+ | |||
+ | Notez que la conversion est réalisée sur les valeurs entrée dans ce cas, ce qui retourne la valeur 4. Si la conversion était réalisée uniquement sur la valeur de sortie, la valeur 5.5 serait arrondie à la valeur 5. | ||
+ | |||
+ | <code cpp | ||
+ | int add(double lhs, double rhs) { | ||
+ | return lhs + rhs; | ||
+ | } | ||
+ | |||
+ | std::cout << add(1.7, 3.8) << std::endl; // affiche 5 | ||
</code> | </code> | ||
Ligne 85: | Ligne 98: | ||
Dans ce cas, le compilateur n'a pas besoin de faire de conversion, il utilise la fonction correspondante aux types des arguments. | Dans ce cas, le compilateur n'a pas besoin de faire de conversion, il utilise la fonction correspondante aux types des arguments. | ||
- | Cependant, cette approche est limitée. Si vous appelez cette fonction avec des arguments de type ''short int'' ou ''float'' (par exemple), le résultat sera automatiquement convertie respectivement en ''int'' et en ''double'' (par promotion). | + | Cependant, cette approche est limitée. Si vous appelez cette fonction avec des arguments de type ''short int'' ou ''float'' (par exemple), le résultat sera automatiquement convertie respectivement en ''int'' et en ''double'' (par promotion). Pour éviter cela, il faudra proposer une surcharge de la fonction ''add'' pour chaque type d'arguments que vous voulez utiliser. Le code n'est pas facilement évolutif, vous devez modifier un code existant si vous ajouter des nouveaux types. Et il devient très vite lourd de devoir écrire toutes les fonctions ''add'' possibles. |
- | Pour éviter cela, il faudra proposer une surcharge de la fonction ''add'' pour chaque type d'arguments que vous voulez utiliser. Le code n'est pas facilement évolutif, vous devez modifier un code existant si vous ajouter des nouveaux types. | + | La programmation générique va permettre de résoudre ce problème, en écrivant des fonctions dont les types des paramètres s'adapteront en fonction des arguments utilisés dans l'appel de fonction. Un exemple de telles fonctions que vous avez déjà rencontrée est les algorithmes de la bibliothèque standard, qui peuvent être appelés sur plusieurs types de conteneurs. |
- | + | ||
- | La programmation générique va permettre de résoudre ce problème, en écrivant des fonctions qui prendront plusieurs types de parametres. Un exemple de telles fonctions que vous avez déjà rencontrée est les algorithmes de la bibliothèque standard, qui peuvent être appelés sur plusieurs types de conteneurs. | + | |
<code cpp> | <code cpp> | ||
std::string s { "azerty" }; | std::string s { "azerty" }; | ||
- | std::sort(std::begin(s), std::end(s)); // ok, trie des chaines | + | std::sort(std::begin(s), std::end(s)); // ok, tri une chaine |
vector<int> v { 1, 3, 5, 2, 4 }; | vector<int> v { 1, 3, 5, 2, 4 }; | ||
- | std::sort(std::begin(v), std::end(v)); // ok, trie des entiers | + | std::sort(std::begin(v), std::end(v)); // ok, tri un tableau |
</code> | </code> | ||
Ligne 102: | Ligne 113: | ||
===== Définir une fonction template ===== | ===== Définir une fonction template ===== | ||
- | Dans une fonction template, un ou plusieurs types utilisés dans la fonction (généralement les types des parametres de fonction ou du retour de la fonction) sont remplacés par un parametre template, pouvant representer plusieurs types. | + | Dans une fonction template, un ou plusieurs types utilisés dans la fonction (généralement les types des paramètres de fonction ou du retour de la fonction) sont remplacés par un paramètre //template//, pouvant représenter plusieurs types. |
- | La syntaxe d'une fonction template est la suivante : | + | La syntaxe d'une fonction //template// est la suivante : |
<code cpp> | <code cpp> | ||
Ligne 111: | Ligne 122: | ||
</code> | </code> | ||
- | La première ligne permet de définir un ou plusieurs paramètres template, qui seront utilisés dans la fonction //comme si c'était des types//. | + | La première ligne permet de définir un ou plusieurs paramètres //template//, qui seront utilisés dans la fonction comme si c'était des types. |
Un paramètre template s'écrit de la façon suivante : | Un paramètre template s'écrit de la façon suivante : | ||
Ligne 119: | Ligne 130: | ||
</code> | </code> | ||
- | Le mot-clé ''typename'' indique que l'identifiant represente un nom de type. L'identifiant respecte les règles habituelles pour écrire un identifiant (contient des lettres minuscules ou majuscules, le caractère ''_'' ou des chiffre sauf en premiere position). | + | Le mot-clé ''typename'' indique que l'identifiant represente un nom de type. L'identifiant respecte les règles habituelles pour écrire un identifiant (contient des lettres minuscules ou majuscules, le caractère ''_'' ou des chiffres sauf en premiere position). |
- | Une liste de parametres template sera constitué de plusieurs parametres template (mot-clé ''typename'' et identifiant), séparés par des virgules. | + | Une liste de paramètres template sera constitué de plusieurs paramètres template (mot-clé ''typename'' et identifiant), séparés par des virgules. |
<code cpp> | <code cpp> | ||
Ligne 127: | Ligne 138: | ||
</code> | </code> | ||
- | Il est classique d'utiliser des majuscules uniquement pour ecrire les parametres template. En particulier, vous verrez souvent des parametres template nommes ''T'', ''U'', etc. Bien sur, il est préférable de donner des noms les plus expressifs possible. | + | Il est classique d'utiliser des majuscules pour écrire les paramètres template. En particulier, vous verrez souvent des paramètres template nommés ''T'', ''U'', etc. Bien sur, il est préférable de donner des noms les plus expressifs possible, mais quand le nom représente "n'importe quoi", c'est moins problématique. |
- | Par exemple, avec la fonction ''add'' precedente : | + | Par exemple, avec la fonction ''add'' précédente : |
<code cpp> | <code cpp> | ||
Ligne 138: | Ligne 149: | ||
</code> | </code> | ||
- | Ce code définit une fonction template qui possède un paramètre template nomme ''T''. Ce paramètre template est utilisée trois fois dans la fonction : dans les deux parametres de fonction en entrée et comme type de retour de la fonction. | + | Ce code définit une fonction template qui possède un paramètre template nomme ''T''. Ce paramètre template est utilisée trois fois dans la fonction : dans les deux paramètres de fonction en entrée et comme type de retour de la fonction. |
<note>**typename et class** | <note>**typename et class** | ||
Ligne 145: | Ligne 156: | ||
</note> | </note> | ||
- | Ce code implique que les types des parametres en entrée et en sortie sont le même type : ''T'' peut être remplacé par n'importe quel type, mais chaque occurrence de ''T'' correspondra toujours au même type. Par exemple, cette fonction ''add'' pourra être appelée avec deux entiers et retourner un entier, ou être appelée avec deux réels retourner un réel, mais elle ne pourra pas prendre en paramètre des entiers et retourner des réels. | + | Ce code implique que les types des paramètres en entrée et en sortie sont le même type : ''T'' peut être remplacé par n'importe quel type, mais chaque occurrence de ''T'' correspondra toujours au même type. Par exemple, cette fonction ''add'' pourra être appelée avec deux entiers et retourner un entier, ou être appelée avec deux réels retourner un réel, mais elle ne pourra pas prendre en paramètre des entiers et retourner des réels. |
- | Il est possible d'utiliser des types différents pour les différents parametres. Par exemple : | + | Il est possible d'utiliser des types différents pour les différents paramètres. Par exemple : |
<code cpp> | <code cpp> | ||
Ligne 172: | Ligne 183: | ||
===== Appeler une fonction template ===== | ===== Appeler une fonction template ===== | ||
- | Lors de l'appel d'une fonction template, le compilateur va remplacer les parametres template par des types concret. Cette étape s'appelle l'instanciation des template. A partir d'une fonction template, le compilateur va générer les fonctions concrète correspondant a chaque type concret qui sont utilisés dans les appels de fonction. | + | Lors de l'appel d'une fonction //template//, le compilateur va remplacer les paramètres //template// par des types concret. Cette étape s'appelle l'instanciation des template. A partir d'une fonction template, le compilateur va générer les fonctions concrète correspondant à chaque type concret qui sont utilisés dans les appels de fonction. |
- | Par exemple, pour la fonction ''add'' precedente, si cette fonction est appelée avec les types ''int'' et ''double'', le compilateur va générer deux fonction surchargées, correspondant a ces types concrets. | + | Par exemple, si la fonction ''add'' précédente est appelée avec les types ''int'' et ''double'', le compilateur va générer deux fonction surchargées, correspondant à ces types concrets. |
<code cpp> | <code cpp> | ||
Ligne 201: | Ligne 212: | ||
</code> | </code> | ||
- | Lorsqu'un paramètre template est utilisé dans plusieurs paramètre de fonction (comme c'est le cas avec la fonction ''add'' precedente), il est nécessaire que les types soient identiques lors de l'appel de fonction, sinon la déduction échoue et cela produit une erreur. | + | Lorsqu'un paramètre template est utilisé dans plusieurs paramètre de fonction (comme c'est le cas avec la fonction ''add'' précédente), il est nécessaire que les types soient identiques lors de l'appel de fonction, sinon la déduction échoué et cela produit une erreur. |
<code cpp> | <code cpp> | ||
Ligne 235: | Ligne 246: | ||
==== Déduction des types et appel explicit ==== | ==== Déduction des types et appel explicit ==== | ||
- | Le code précédent est la façon la plus simple d'appeler une fonction template. La syntaxe est identique à un appel de fonction classique, la seule différence est que le compilateur ajoute deux étapes lors de l'appel : | + | Le code précédent est la façon la plus simple d'appeler une fonction //template//. La syntaxe est identique à un appel de fonction classique, la seule différence est que le compilateur ajoute deux étapes lors de l'appel : |
- | - la déduction des types : le compilateur regarde les types des arguments dans l'appel de la fonction et déduit les types à utiliser ; | + | * la déduction des types : le compilateur regarde les types des arguments dans l'appel de la fonction et déduit les types à utiliser ; |
- | - l'instanciation des template : pour chaque combinaison de types déduits, le compilateur génère une fonction avec des types concrets. | + | * l'instanciation des //templates// : pour chaque combinaison de types déduits, le compilateur génère une fonction avec des types concrets. |
- | Cependant, il n'est pas toujours possible de déduire les types lors de l'appel de la fonction. La déduction des types n'est possible que pour les parametres template utilisés comme paramètre de fonction, pas les parametres template utilisés en retour de fonction ou dans le corps de la fonction. Vous pouvez egalement souhaitez appeler une fonction template en forçant l'utilisation d'argument template spécifique. | + | Cependant, il n'est pas toujours possible de déduire les types lors de l'appel de la fonction. La déduction des types n'est possible que pour les paramètres //template// utilisés comme paramètre de fonction, pas les paramètres //template// utilisés en retour de fonction ou dans le corps de la fonction. Vous pouvez egalement souhaitez appeler une fonction template en forçant l'utilisation d'arguments //templates// spécifiques. |
<code cpp> | <code cpp> | ||
Ligne 251: | Ligne 262: | ||
</code> | </code> | ||
- | Dans ce cas, il faut expliciter les types que vous souhaitez utiliser pour l'instanciation des templates. La syntaxe est la suivante : | + | Dans ce cas, il faut expliciter les types que vous souhaitez utiliser pour l'instanciation des templates. La syntaxe pour appeler la fonction est la suivante : |
<code cpp> | <code cpp> | ||
Ligne 257: | Ligne 268: | ||
</code> | </code> | ||
- | La différence avec un appel de fonction classique est donc cette liste d'arguments template ajoutée entre chevrons après le nom de la fonction. | + | La différence avec un appel de fonction classique est donc cette liste d'arguments template ajoutée entre les chevrons après le nom de la fonction. |
- | Le code precedent devient, par exemple : | + | Le code précédent devient, par exemple : |
<code cpp> | <code cpp> | ||
Ligne 271: | Ligne 282: | ||
</code> | </code> | ||
- | <note>**Parametres et arguments** | + | <note>**Paramètres et arguments** |
- | Notez la similitude des termes utilisés entre parametres et arguments de fonction et parametres et argument template : les parametres apparaissent dans la declaration des fonctions et les arguments dans les appels de fonction. | + | Notez la similitude des termes utilisés entre "paramètre" et "argument" de fonction et "paramètre" et "argument" //template// : les paramètres apparaissent dans la déclaration des fonctions et les arguments dans les appels de fonction. |
- | Pour les parametres et arguments de fonction : | + | Pour les paramètres et arguments de fonction : |
<code cpp> | <code cpp> | ||
- | void f(int a, int b, int x) {} // a, b et c = parametres de fonction | + | void f(int a, int b, int x) {} // a, b et c = paramètres de fonction |
int main() { | int main() { | ||
Ligne 285: | Ligne 296: | ||
</code> | </code> | ||
- | Pour les parametres et arguments template : | + | Pour les paramètres et arguments //template// : |
<code cpp> | <code cpp> | ||
- | template<typename T, typename U> // T et U = parametres template | + | template<typename T, typename U> // T et U = paramètres template |
void f(T a, U b) {} | void f(T a, U b) {} | ||
Ligne 310: | Ligne 321: | ||
</code> | </code> | ||
- | Notez que lorsque l'argument template est spécifié, les arguments de fonction sont convertie, si nécessaire, pour s'adapter à l'argument template. Dans ce code, la valeur ''1.2'' (''double'') est convertie (et arrondie) en ''1'' (''int''). | + | Notez que lorsque l'argument template est spécifié, les arguments de fonction sont convertie, si nécessaire, pour s'adapter à l'argument template (il ne peut pas y avoir d'échec de déduction des types, puisque cette étape n'est pas réalisée). Dans ce code, la valeur ''1.2'' (''double'') est convertie (et arrondie) en ''1'' (''int''). |
- | Il est possible de mélanger déduction de types et arguments template explicite dans un appel de fonction template. Dans ce cas, les arguments template explicite correspondent aux premiers parametres template, les autres parametres template sont déduits. | + | Il est possible de mélanger déduction de types et arguments //template// explicite dans un appel de fonction template. Dans ce cas, les arguments template explicite correspondent aux premiers paramètres template, les autres paramètres template sont déduits. |
<code cpp> | <code cpp> | ||
Ligne 327: | Ligne 338: | ||
</code> | </code> | ||
- | Lors du premier appel de la fonction ''add'', les parametre template ''T'' et ''U'' sont déduits des arguments de fonction (''int'' et ''double''). Lors du deuxième appel, le paramètre template ''T'' est explicite (''float''), le second paramètre ''U'' est déduits (''double''). Lors du dernier appel, les deux parametres template sont deduits (''float'' et ''float''). | + | Lors du premier appel de la fonction ''add'', les parametre //template// ''T'' et ''U'' sont déduits des arguments de fonction (''int'' et ''double''). Lors du deuxième appel, le paramètre //template// ''T'' est explicite (''float''), le second paramètre ''U'' est déduits (''double''). Lors du dernier appel, les deux paramètres template sont déduits (''float'' et ''float''). |
===== Type par defaut ===== | ===== Type par defaut ===== | ||
- | Pour pour les parametres de fonction, il est possible de specifier un paramètre template par défaut lors de la déclaration d'un template. Lorsque l'argument template n'est pas spécifié lors de l'appel de la fonction template et que la déduction des types n'est pas possible, ce type par défaut sera utilisé. | + | Pour pour les paramètres de fonction, il est possible de spécifier un paramètre template par défaut lors de la déclaration d'un template. Lorsque l'argument template n'est pas spécifié lors de l'appel de la fonction template et que la déduction des types n'est pas possible, ce type par défaut sera utilisé. |
La syntaxe pour indiquer un type par défaut est la suivante : | La syntaxe pour indiquer un type par défaut est la suivante : | ||
Ligne 359: | Ligne 370: | ||
Les fonctions template peuvent être surchargées, entre elles et avec les fonctions classiques. Cette fois-ci aussi, le compilateur travaille par étapes : | Les fonctions template peuvent être surchargées, entre elles et avec les fonctions classiques. Cette fois-ci aussi, le compilateur travaille par étapes : | ||
- | - le compilateur instancie les fonctions fonctions template ; | + | * le compilateur instancie les fonctions //templates// ; |
- | - puis la résolution de la surcharge est réalisée sur l'ensemble des fonctions. | + | * puis la résolution de la surcharge est réalisée sur l'ensemble des fonctions. |
- | La resolution de la surcharge est similaire a celle sans fonction template, les fonctions template ont une priorité intermédiaire entre les fonctions sans conversion et avec conversion | + | La résolution de la surcharge est similaire à celle sans fonction template, les fonctions template ont une priorité intermédiaire entre les fonctions sans conversion et avec conversion : |
- | - appel de fonction sans aucune conversion ; | + | * appel de fonction sans aucune conversion ; |
- | - appel de fonction template | + | * appel de fonction template |
- | - appel de fonction avec promotion ; | + | * appel de fonction avec promotion ; |
- | - appel de fonction avec conversion. | + | * appel de fonction avec conversion. |
Voici un exemple pour que les choses soient plus concrètes. | Voici un exemple pour que les choses soient plus concrètes. | ||
Ligne 391: | Ligne 402: | ||
Dans ce code, le premier appel de la fonction ''f'' contient deux arguments de type ''int''. La fonction fonction template ''#1'' sera instancié avec le paramètre ''T = int'', ce qui produira une fonction avec la signature suivante : ''f(int, int)''. | Dans ce code, le premier appel de la fonction ''f'' contient deux arguments de type ''int''. La fonction fonction template ''#1'' sera instancié avec le paramètre ''T = int'', ce qui produira une fonction avec la signature suivante : ''f(int, int)''. | ||
- | Lors de la resolution de la surcharge, les deux fonctions ont donc la même signature, mais l'une est une instance de fonction template et n'est donc pas prioritaire. La fonction ''#2'' est donc choisie par le compilateur. | + | Lors de la résolution de la surcharge, les deux fonctions ont donc la même signature, mais l'une est une instance de fonction //template// et n'est donc pas prioritaire. La fonction ''#2'' est donc choisie par le compilateur. |
- | Dans le second appel de la fonction ''f'', celle-ci contient des arguments de types ''int'' et ''double''. La fonction template sera donc instanciée avec la signature suivante : ''f(int, double)''. Lors de la resolution de la surcharge, le compilateur a le choix entre une fonction template dont les types des parametres correspondent aux arguments, et une fonction non template qui nécessite une conversion de ''double'' en ''int'' pour être appelée. La fonction template ''#1'' est donc choisie. | + | Dans le second appel de la fonction ''f'', celle-ci contient des arguments de types ''int'' et ''double''. La fonction //template// sera donc instanciée avec la signature suivante : ''f(int, double)''. Lors de la résolution de la surcharge, le compilateur a le choix entre une fonction //template// dont les types des paramètres correspondent aux arguments, et une fonction non template qui nécessite une conversion de ''double'' en ''int'' pour être appelée. La fonction template ''#1'' est donc choisie. |
===== Echec d'instanciation ===== | ===== Echec d'instanciation ===== | ||
- | Lors de l'utilisation des templates, vous pouvez rencontrer deux types d'erreurs : | + | Lors de l'utilisation des //templates//, vous pouvez rencontrer deux types d'erreurs : |
- | - lors de la déduction des types, comme vous l'avez précédemment ("template argument deduction/substitution failed") ; | + | * lors de la déduction des types, comme vous l'avez précédemment ("template argument déduction/substitution failed") ; |
- | - lors de l'appel de la fonction après instanciation. | + | * lors de l'appel de la fonction après instanciation. |
En effet, comme le compilateur fonctionne par étape, il ne se préoccupe pas du corps de la fonction lorsqu'il instancie les fonctions templates. Il est donc possible qu'une fonction template soit instanciée, mais que la fonction n'a pas de sens. | En effet, comme le compilateur fonctionne par étape, il ne se préoccupe pas du corps de la fonction lorsqu'il instancie les fonctions templates. Il est donc possible qu'une fonction template soit instanciée, mais que la fonction n'a pas de sens. | ||
Ligne 432: | Ligne 443: | ||
</code> | </code> | ||
- | Et la se pose un problème : l'operateur d'addition ''+'' n'a pas de sens pour le type ''const char*''. Ce code produit donc l'erreur suivante ("invalid operands ... to binary operator+") : | + | Et la se pose un problème : l'opérateur d'addition ''+'' n'a pas de sens pour le type ''const char*''. Ce code produit donc l'erreur suivante ("invalid operands ... to binary operator+") : |
<code> | <code> | ||
Ligne 446: | Ligne 457: | ||
===== Assertion sur les types ===== | ===== Assertion sur les types ===== | ||
- | Vous connaissez déjà les assertions, qui permettent de vérifier à l'exécution qu'une condition est vrai. | + | Vous connaissez déjà les assertions, qui permettent de vérifier à l'exécution qu'une condition est vraie. |
<code cpp> | <code cpp> | ||
Ligne 463: | Ligne 474: | ||
</code> | </code> | ||
- | Il existe un second type d'assertion, qui permet de vérifier une condition à la compilation. Comme les types (et donc les templates) sont résolues à la compilation, ce type d'assertion va permettre d'imposer des conditions sur les parametres templates. | + | Il existe un second type d'assertion, qui permet de vérifier une condition à la compilation. Comme les types (et donc les //templates//) sont résolues à la compilation, ce type d'assertion va permettre d'imposer des conditions sur les parametres templates. |
La syntaxe est la suivante : | La syntaxe est la suivante : | ||
Ligne 471: | Ligne 482: | ||
</code> | </code> | ||
- | Note : contrairement à ''assert'' qui nécessaire d'inclure un fichier d'en-tête (''cassert''), ''static_assert'' est un mot-clé du langage et ne nécessite pas d'inclusion. | + | Note : contrairement à ''assert'' qui nécessaire d'inclure un fichier d'en-tête (''<cassert>''), ''static_assert'' est un mot-clé du langage et ne nécessite pas d'inclusion. |
- | Et pour ecrire des conditions sur les types, vous pouvez utiliser les fonctionnalités permettant d'obtenir des informations sur les types que vous avez vu dans le chapitre : [[informations_sur_les_types|]]. | + | Et pour écrire des conditions sur les types, vous pouvez utiliser les fonctionnalités permettant d'obtenir des informations sur les types que vous avez vu dans le chapitre : [[informations_sur_les_types|]]. |
Pour prendre un exemple concret, imaginez que vous souhaitez limiter l'utilisation de votre fonction ''add'' uniquement aux types représentant un nombre (et donc interdire l'utilisation de votre fonction avec une chaine de caracteres par exemple). Vous pouvez pour cela utiliser ''static_assert'' avec ''is_arithmetic''. | Pour prendre un exemple concret, imaginez que vous souhaitez limiter l'utilisation de votre fonction ''add'' uniquement aux types représentant un nombre (et donc interdire l'utilisation de votre fonction avec une chaine de caracteres par exemple). Vous pouvez pour cela utiliser ''static_assert'' avec ''is_arithmetic''. | ||
Ligne 492: | Ligne 503: | ||
</code> | </code> | ||
- | Le premier appel à la fonction ''add'', le paramètre template ''T'' est instancié en utilisant le type ''int''. L'assertion est vraie et cela ne produit pas d'erreur. | + | Dans le premier appel à la fonction ''add'', le paramètre //template// ''T'' est instancié en utilisant le type ''int''. L'assertion est vraie et cela ne produit pas d'erreur. |
Le second appel n'est pas un type de nombre et produit une assertion : | Le second appel n'est pas un type de nombre et produit une assertion : | ||
Ligne 558: | Ligne 569: | ||
</code> | </code> | ||
- | Vous pouvez alors vous demander pourquoi la fonction template, qui est manifestement problématique, ne produit également pas une erreur dans le premier code ? | + | Vous pouvez alors vous demander pourquoi la fonction //template//, qui est manifestement problématique, ne produit également pas une erreur dans le premier code ? |
- | La raison est que l'échec de l'instanciation des template ne produit pas d'erreur (c'est ce que signifie "Substitution failure is not an error" : "l'echec d'une substitution n'est pas une erreur). Ce qui produit une erreur est le fait que le compilateur ne trouve aucune fonction valide dans le second code, pas l'échec de l'instanciation. | + | La raison est que l'échec de l'instanciation des template ne produit pas d'erreur (c'est ce que signifie "Substitution failure is not an error" : "l'echec d'une substitution n'est pas une erreur"). Ce qui produit une erreur est le fait que le compilateur ne trouve aucune fonction valide dans le second code, pas l'échec de l'instanciation. |
- | Dans cet exemple, le SFINAE a ete utilise sans le savoir. Mais il existe de nombreuses techniques de méta-programmation qui utilise ce concept. Cela signifie en particulier que vous pouvez ecrire autant de fonctions template que vous voulez, du moment qu'au moins une fonction est valide, le code compilera sans erreur. | + | Dans cet exemple, le SFINAE a ete utilise sans le savoir. Mais il existe de nombreuses techniques de méta-programmation qui utilise ce concept. Cela signifie en particulier que vous pouvez écrire autant de fonctions template que vous voulez, du moment qu'au moins une fonction est valide, le code compilera sans erreur. |
===== Un exemple d'application : les algorithmes de la bibliothèque standard ===== | ===== Un exemple d'application : les algorithmes de la bibliothèque standard ===== | ||
- | Supposez que vous écrivez un algorithme qui prend en paramètre une collection de bibliothèque standard (par exemple ''std::vector<int>''). Vous pourriez ecrire une fonction qui prend en parametre cette collection. Par exemple : | + | Supposez que vous écrivez un algorithme qui prend en paramètre une collection de bibliothèque standard (par exemple ''std::vector<int>''). Vous pourriez écrire une fonction qui prend en parametre cette collection. Par exemple : |
<code cpp> | <code cpp> | ||
Ligne 573: | Ligne 584: | ||
</code> | </code> | ||
- | Comme ce chapitre est consacré a la généricité, vous avez surement compris le problème : ce code n'est pas générique (vous ne pouvez pas l'utiliser avec n'importe quel type de collection). | + | Comme ce chapitre est consacré à la généricité, vous avez surement compris le problème : ce code n'est pas générique (vous ne pouvez pas l'utiliser avec n'importe quel type de collection). |
Vous pouvez améliorer les choses en transformant cette fonction en template. Par exemple : | Vous pouvez améliorer les choses en transformant cette fonction en template. Par exemple : | ||
Ligne 584: | Ligne 595: | ||
Cette fonction est un peu plus générique, il est maintenant possible de changer le type d'éléments dans la collection. Cependant, ce n'est pas encore totalement générique : il n'est pas possible de changer le type de collection, cela sera forcement un ''std::vector''. | Cette fonction est un peu plus générique, il est maintenant possible de changer le type d'éléments dans la collection. Cependant, ce n'est pas encore totalement générique : il n'est pas possible de changer le type de collection, cela sera forcement un ''std::vector''. | ||
- | Vous pourriez alors ecrire le code suivant : | + | Vous pourriez alors écrire le code suivant : |
<code cpp> | <code cpp> | ||
Ligne 605: | Ligne 616: | ||
</code> | </code> | ||
- | Comme vous pouvez vous en douter, cela est possible en utilisant les templates. | + | Comme vous pouvez vous en douter, cela est possible en utilisant les //templates//. |
- | La signature de la fonction ''sort'' est une fonction template, qui prend en parametre template le type d'iterateurs (voir la documentation sur cppreference : [[http://en.cppreference.com/w/cpp/algorithm/sort|sort]]) : | + | La signature de la fonction ''std::sort'' est une fonction //template//, qui prend en parametre //template// le type d'iterateurs (voir la documentation sur cppreference : [[http://en.cppreference.com/w/cpp/algorithm/sort|sort]]) : |
<code cpp> | <code cpp> | ||
Ligne 614: | Ligne 625: | ||
</code> | </code> | ||
- | Dans ce code, le paramètre template a été nommé ''RandomIt'', pour indiquer que c'est un itérateur de type "random" (pour rappel, voir le chapitre [[autres_collections|]]). | + | Dans ce code, le paramètre //template// a été nommé ''RandomIt'', pour indiquer que c'est un itérateur de type "random" (pour rappel, voir le chapitre [[autres_collections|]]). |
Lorsque vous écrirez vos propres algorithmes, essayez de respecter cette signature pour les fonctions. Cela permettra la plus grande flexibilité dans le code et garantira que vos algorithmes soient compatibles avec les collections de la bibliotheque standard. | Lorsque vous écrirez vos propres algorithmes, essayez de respecter cette signature pour les fonctions. Cela permettra la plus grande flexibilité dans le code et garantira que vos algorithmes soient compatibles avec les collections de la bibliotheque standard. | ||
Ligne 621: | Ligne 632: | ||
===== Template et meta-programmation ===== | ===== Template et meta-programmation ===== | ||
- | Ce chapitre est une introduction aux fonctions template et à la programmation générique. L'utilisation des template est donc limitée au stricte minimum. Mais il faut savoir que les templates en C++ sont beaucoup plus puissant que cela et forme un véritable langage de programme dans le C++. Ce méta-langage propose les fonctionnalites classiques d'un langage de programmation, en particulier la possibilité de faire des tests et des boucles. | + | Ce chapitre est une introduction aux fonctions //template// et à la programmation générique. L'utilisation des //template// est donc limitée au stricte minimum. Mais il faut savoir que les templates en C++ sont beaucoup plus puissant que cela et forme un véritable langage de programme dans le C++. Ce méta-langage propose les fonctionnalites classiques d'un langage de programmation, en particulier la possibilité de faire des tests et des boucles. |
Cette méta-programmation présente un avantage très spécifique : elle ne fonctionne que lors de la compilation, pas lors de l'exécution. Elle permet donc d'écrire du code de haut niveau, qui va adapter le comportement du code en fonction des types, faire des vérifications avancées sur la qualité du code, et cela sans aucun coût à l'exécution du programme. | Cette méta-programmation présente un avantage très spécifique : elle ne fonctionne que lors de la compilation, pas lors de l'exécution. Elle permet donc d'écrire du code de haut niveau, qui va adapter le comportement du code en fonction des types, faire des vérifications avancées sur la qualité du code, et cela sans aucun coût à l'exécution du programme. | ||
Ligne 628: | Ligne 639: | ||
- | ^ [[surcharge_fonctions|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[fonctions_lambdas|Chapitre suivant]] ^ | + | ^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^ |