This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
cpp:syntax:functions:generic [2021/04/06 12:45] bmonsuez |
cpp:syntax:functions:generic [2022/11/18 10:47] (current) |
||
---|---|---|---|
Line 10: | Line 10: | ||
{ | { | ||
for(int index = 0; index < theArrayLength; index ++) | for(int index = 0; index < theArrayLength; index ++) | ||
- | if(anArray[index] == anArray) | + | if(anArray[index] == theValue) |
return index; | return index; | ||
return -1; | return -1; | ||
Line 24: | Line 24: | ||
{ | { | ||
for(int index = 0; index < theArrayLength; index ++) | for(int index = 0; index < theArrayLength; index ++) | ||
- | if(anArray[index] == anArray) | + | if(anArray[index] == theValue) |
return index; | return index; | ||
return -1; | return -1; | ||
Line 36: | Line 36: | ||
- | ===== Les fonctions paramétrables ? ===== | + | ===== Les fonctions paramétrées par des types ===== |
C++ propose de définir des fonctions paramétrables par le type des arguments. Ainsi la fonction précédente peut-être paramétrer par le type des éléments stockés dans le tableau comme suit : | C++ propose de définir des fonctions paramétrables par le type des arguments. Ainsi la fonction précédente peut-être paramétrer par le type des éléments stockés dans le tableau comme suit : | ||
Line 46: | Line 46: | ||
{ | { | ||
for(int index = 0; index < theArrayLength; index ++) | for(int index = 0; index < theArrayLength; index ++) | ||
- | if(anArray[index] == anArray) | + | if(anArray[index] == theValue) |
return index; | return index; | ||
return -1; | return -1; | ||
Line 54: | Line 54: | ||
Avec cette nouvelle syntaxe, nous définissons un **patron** ou un **modèle** (en anglais __template__) qui définit une famille de fonctions : | Avec cette nouvelle syntaxe, nous définissons un **patron** ou un **modèle** (en anglais __template__) qui définit une famille de fonctions : | ||
- | * qui s'appelle ''index_of'', | + | * qui s'appellent ''index_of'', |
* qui prennent trois paramètres : | * qui prennent trois paramètres : | ||
- un pointeur sur un tableau d'éléments, les éléments ayant chacun pour type ''valueT'' qui est un type paramètre, | - un pointeur sur un tableau d'éléments, les éléments ayant chacun pour type ''valueT'' qui est un type paramètre, | ||
Line 70: | Line 70: | ||
{ | { | ||
for(int index = 0; index < theArrayLength; index ++) | for(int index = 0; index < theArrayLength; index ++) | ||
- | if(anArray[index] == anArray) | + | if(anArray[index] == theValue) |
return index; | return index; | ||
return -1; | return -1; | ||
} | } | ||
- | int* arrayOfIntegers = { 1, 3, 4, 2, 9, 5, 7, 8, 6 }; | + | int arrayOfIntegers[9] = { 1, 3, 4, 2, 9, 5, 7, 8, 6 }; |
int main() | int main() | ||
Line 93: | Line 93: | ||
* Il n'existe pas de fonction ''index_of(int*, int, int)''. | * Il n'existe pas de fonction ''index_of(int*, int, int)''. | ||
- | * Il existe une fonction __template__, donc un modèle de fonction qui prend trois arguments : ''valueT*'', ''int'', ''valueT''.\ Si on remplace ''valueT'' par ''int'', on génèrera à partir du modèle ''index_of(valueT*, int, valueT)'' une fonction ''index_of(int*, int, int)''.\ Le compilateur génère la fonction ''index_of(int*, int, int)'' en remplaçant ''valueT'' par ''int'' et en créant ainsi la fonction ''index_of(valueT*, int, valueT)''. | + | * Il existe une fonction __template__, donc un modèle de fonction qui prend trois arguments : ''valueT*'', ''int'', ''valueT''.\\ Si on remplace ''valueT'' par ''int'', on génèrera à partir du modèle ''index_of(valueT*, int, valueT)'' une fonction ''index_of(int*, int, int)''.\\ Le compilateur génère la fonction ''index_of(int*, int, int)'' en remplaçant ''valueT'' par ''int'' et en créant ainsi la fonction ''index_of(valueT*, int, valueT)''. |
- | ===== Ressources externes associées ===== | + | ===== Fonctions génériques & fonctions spécialisées ===== |
+ | |||
+ | Si nous considérons la fonction précédente et que nous souhaitons l'utiliser pour chercher des tableaux contenant des chaînes de caractères : | ||
+ | |||
+ | <code cpp> | ||
+ | |||
+ | const char* tokens[6] = { "if", "else", "try", "catch", "switch", "case" }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | std::string token = "try"; | ||
+ | std::cout << index_of(tokens, 6, token.c_str()); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | |||
+ | L'exécution de ce code va retourner non pas l'indice ''2'' comme nous pourrions attendre mais au contraire ''-1''. Le résultat est normal, puisque nous testons non pas l'égalité de deux chaînes de caractères et de deux pointeurs qui pointent sur le premier caractère d'une chaîne de caractères. Et bien entendu, ces deux pointeurs sont différents si les deux chaînes de caractères sont distinctes même si le contenu des deux chaînes de caractères sont identifiques. | ||
+ | |||
+ | |||
+ | Nous pouvons définir une version spécialisée du code : | ||
+ | <code cpp> | ||
+ | |||
+ | #include<iostream> | ||
+ | #include<string> | ||
+ | #include<cstring> | ||
+ | |||
+ | template<class valueT> | ||
+ | int index_of(valueT* anArray, int theArrayLength, valueT theValue) | ||
+ | { | ||
+ | for(int index = 0; index < theArrayLength; index ++) | ||
+ | if(anArray[index] == theValue) | ||
+ | return index; | ||
+ | return -1; | ||
+ | } | ||
+ | |||
+ | template<> | ||
+ | int index_of(const char** anArray, int theArrayLength, const char* theValue) | ||
+ | { | ||
+ | for(int index = 0; index < theArrayLength; index ++) | ||
+ | if(std::strcmp(anArray[index], theValue) == 0) | ||
+ | return index; | ||
+ | return -1; | ||
+ | } | ||
+ | |||
+ | const char* tokens[6] = { "if", "else", "try", "catch", "switch", "case" }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | std::string token = "try"; | ||
+ | std::cout << index_of(tokens, 6, token.c_str()); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Dans ce cas, comme il existe une version spécialisée qui correspond au type des arguments, la fonction qui sera appellée sera | ||
+ | |||
+ | <code cpp> | ||
+ | template<> | ||
+ | int index_of(const char** anArray, int theArrayLength, const char* theValue) | ||
+ | </code> | ||
+ | |||
+ | de préférence à la fonction : | ||
+ | |||
+ | <code cpp> | ||
+ | template<class valueT> | ||
+ | int index_of(valueT* anArray, int theArrayLength, valueT theValue) | ||
+ | </code> | ||
+ | |||
+ | En effet, la fonction spécialisée est considérée comme prioritaire sur la fonction non spécialisée. Et c'est ainsi que nous allons exécuter le code qui ne compare plus les pointeurs mais bien les chaînes de caractères en appelant la fonction [[https://en.cppreference.com/w/c/string/byte/strcmp|''strcmp'']] qui effectue cette comparaison. | ||
+ | |||
+ | ==== Fonctions partiellement spécialisées ==== | ||
+ | |||
+ | Dans l'exemple précédent, nous avions une fonction complétement spécialisée, l'ensemble des paramètres de type était défini. Nous pouvons définir des spécialisations partielles de fonctions. | ||
+ | |||
+ | Considérons le cas d'une fonction ''is_pointer'' qui doit retourner ''true'' si l'argument passé en paramètre est un pointeur et ''false'' dans tous les autres cas. | ||
+ | |||
+ | Le comportement par défaut de la fonction est de retourner ''false''. | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | bool is_pointer(T anItem) { return false; } | ||
+ | </code> | ||
+ | |||
+ | Si l'argument est de type pointeur, alors la fonction doit retournée ''true''. Nous pouvons introduire les deux spécialisations suivantes : | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | bool is_pointer(const T* anItem) { return true; } | ||
+ | template<class T> | ||
+ | bool is_pointer(T* anItem) { return true; } | ||
+ | </code> | ||
+ | |||
+ | |||
+ | Donc maintenant, que va-t-il se passer. Quand on va appeller la fonction avec un objet ayant un certain type, le compilateur va regarder si le type de l'argument est de type ''T*'' ou ''const T*'' et dans ce cas, il va appeller l'une des deux fonctions : | ||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | bool is_pointer(const T* anItem) { return true; } | ||
+ | template<class T> | ||
+ | bool is_pointer(T* anItem) { return true; } | ||
+ | </code> | ||
+ | et retournera le résultat ''true''. Sinon, il appellera la fonction générique non spécialisée et il retournera le résultat ''false''. | ||
+ | |||
+ | Au passage, ce calcul s'effectue uniquement à la compilation. C'est d'ailleurs pour cela que C++ a introduit avec la version 2020 l'annotation [[https://en.cppreference.com/w/cpp/language/consteval|''consteval'']] pour indiquer que le résultat de la fonction est calculable à la compilation et qu'il n'est pas besoin de générer de code pour l'exécution. On retrouve ici l'objectif de performance de C++. En C++ 20, nous écrirons : | ||
+ | |||
+ | <code cpp> | ||
+ | template<class T> | ||
+ | consteval bool is_pointer(T anItem) { return false; } | ||
+ | template<class T> | ||
+ | consteval bool is_pointer(const T* anItem) { return true; } | ||
+ | template<class T> | ||
+ | consteval bool is_pointer(T* anItem) { return true; } | ||
+ | </code> | ||
+ | |||
+ | ===== Pour aller plus loin ===== | ||
[[https://en.cppreference.com/w/cpp/language/function_template|Template functions]] | [[https://en.cppreference.com/w/cpp/language/function_template|Template functions]] | ||
+ | |||
+ | [[https://en.cppreference.com/w/cpp/language/template_parameters|Template parameters and template arguments]] | ||
[[https://en.cppreference.com/w/cpp/language/template_argument_deduction|Template argument deduction]] | [[https://en.cppreference.com/w/cpp/language/template_argument_deduction|Template argument deduction]] |