Un nom, plusieurs versions
Tu as souvent besoin de la même opération pour différents types de données : afficher un int, afficher une string, afficher un double. Dans certains langages, tu inventerais printInt, printString, printDouble. C++ te permet de leur donner à toutes le même nom et les distingue par leurs paramètres. C'est la surcharge de fonctions.
La règle est simple : plusieurs fonctions peuvent partager un nom tant que leurs listes de paramètres diffèrent - par le nombre de paramètres, leurs types ou leur ordre. Le compilateur examine les arguments à chaque point d'appel et choisit pour toi la version qui correspond.
Trois fonctions, un seul nom. Chaque appel atterrit sur la version dont le type de paramètre convient à l'argument. C'est ce qui fait que std::cout << x fonctionne aussi bien pour des ints, des doubles et des strings - operator<< est surchargé de très nombreuses fois.
Ce qui compte comme une surcharge distincte
Une surcharge se distingue uniquement par sa liste de paramètres. Tu peux faire varier :
int area(int side); // 1 paramètre
int area(int width, int height); // 2 paramètres -> différente
double area(double r); // type différent -> différente
void log(string msg, int level); // l'ordre compte...
void log(int level, string msg); // ...donc celle-ci est différente aussi
Chacune d'elles est une surcharge légale et distincte. Le compilateur construit un ensemble de candidates à partir de toutes les fonctions nommées area, puis effectue la correspondance par nombre et type d'arguments.
Le type de retour seul ne suffit pas
Voici le piège qui fait trébucher presque tout le monde : tu ne peux pas surcharger sur le type de retour. La valeur de retour ne joue aucun rôle dans le choix de la surcharge, car le compilateur décide quelle fonction appeler à partir des arguments - bien avant de regarder ce qui est renvoyé.
int convert(double x); // OK
double convert(double x); // ERREUR : redéfinition - seul le type de retour diffère
Cela ne compilera pas. Si les listes de paramètres sont identiques, les deux déclarations sont la même fonction du point de vue de la surcharge, et tu obtiens une erreur de redéfinition. Pour brancher selon le type de résultat, change un paramètre (ou utilise des templates / un transtypage avec static_cast au point d'appel).
Comment la résolution de surcharge désigne un gagnant
Lorsque tu fais un appel, le compilateur classe toutes les surcharges viables et choisit la meilleure correspondance. Grosso modo, il préfère, dans cet ordre :
- Une correspondance exacte (aucune conversion nécessaire).
- Une promotion (par ex.
charoushort->int,float->double). - Une conversion standard (par ex.
int->double,double->int, pointeur vers la base).
Si exactement une surcharge est strictement meilleure que toutes les autres, c'est elle qui l'emporte. Regarde comment une correspondance exacte bat une conversion :
'A' est un char, mais une promotion en int l'emporte sur une conversion en double, donc l'appel cible la surcharge int. Ces règles de classement expliquent pourquoi la résolution de surcharge « fait généralement ce qu'il faut » - et te surprend de temps en temps.
Le piège de l'ambiguïté
Si deux surcharges sont également bonnes - aucune strictement meilleure - le compilateur refuse de deviner et signale un appel ambigu. Le cas d'école : deux surcharges qui nécessitent chacune une conversion de même rang :
void f(int x);
void f(double x);
f(0L); // ERREUR : ambigu - long -> int et long -> double sont des conversions de même rang
Ni int ni double n'est une correspondance exacte pour long, et les deux conversions se situent au même rang, donc l'appel est ambigu. Tu as deux solutions propres :
Une surprise apparentée : passer un littéral de chaîne. void g(const string&) et void g(bool) se disputeront toutes deux g("hi"), et bool peut gagner, car un const char* se convertit en bool (non nul -> true) en moins d'étapes que la construction d'un std::string. Si tu vois un jour un littéral de chaîne appeler mystérieusement ta surcharge bool, c'est la raison - ajoute une surcharge const char* ou const string& pour qu'elle prenne la correspondance exacte.
Surcharge et arguments par défaut font mauvais ménage
Les arguments par défaut ne remplacent pas la surcharge, et combiner les deux crée de l'ambiguïté. Chacun peut répondre au même appel, donc le compilateur ne peut pas choisir :
void connect(string host, int port = 8080); // appelable avec 1 argument
void connect(string host); // également appelable avec 1 argument
connect("localhost"); // ERREUR : ambigu - les deux correspondent à un seul argument
Choisis une seule approche par forme d'appel. Utilise les arguments par défaut quand le comportement est identique et que tu veux simplement des paramètres optionnels ; utilise la surcharge quand des listes d'arguments différentes doivent exécuter du code réellement différent. Les mélanger de sorte que deux signatures entrent en collision pour le même nombre d'arguments est une erreur d'ambiguïté garantie.
Une dernière distinction à bien clouer : la surcharge n'est pas la redéfinition. La surcharge se résout à la compilation entre des fonctions de même portée portant le même nom mais aux paramètres différents. La redéfinition remplace une fonction virtual dans une classe dérivée à l'exécution et exige la même signature - un sujet pour les fonctions virtuelles plus tard.
Ensuite : les lambdas
La surcharge donne à un nom plusieurs implémentations typées choisies à la compilation. Parfois, cependant, tu ne veux pas du tout d'une fonction nommée - tu as besoin d'une petite fonction jetable définie là où tu l'utilises, souvent pour la passer à un algorithme comme sort. C'est exactement ce que sont les lambdas : des fonctions anonymes que tu peux écrire en ligne, avec lesquelles tu captures les variables environnantes et que tu transmets en une seule expression. Ensuite, nous verrons comment les écrire et quand elles surpassent une fonction nommée complète.
Questions fréquentes
Qu'est-ce que la surcharge de fonctions en C++ ?
La surcharge de fonctions te permet de définir plusieurs fonctions portant le même nom tant que leurs listes de paramètres diffèrent (par le nombre, le type ou l'ordre). Le compilateur choisit laquelle appeler selon les arguments que tu passes, donc print(42) et print("hi") peuvent appeler deux fonctions print différentes.
Deux fonctions C++ peuvent-elles différer uniquement par leur type de retour ?
Non. Les surcharges doivent différer dans leur liste de paramètres. int f(int) et double f(int) provoquent une erreur de compilation - le type de retour ne fait pas partie de la signature utilisée pour la résolution de surcharge, car le compilateur choisit la surcharge à partir des arguments au point d'appel, avant même que la valeur de retour ne soit utilisée.
Qu'est-ce qui provoque une erreur d'« appel ambigu » avec des fonctions surchargées ?
Cela se produit quand deux surcharges correspondent aussi bien l'une que l'autre et que le compilateur ne peut en privilégier aucune. Un cas classique : f(int) et f(double) appelées avec f(0L) (un long), où chacune nécessite une conversion de même rang. Corrige-le en ajoutant une surcharge à correspondance exacte ou en convertissant l'argument vers le type voulu.