21 Funciones IV: Sobrecarga

Anteriormente hemos visto operadores que tienen varios usos, como por ejemplo *, &, << o >>. Esto es lo que se conoce en C++ como sobrecarga de operadores. Con las funciones existe un mecanismo análogo, de hecho, en C++, los operadores no son sino un tipo especial de funciones, aunque eso sí, algo peculiares.

Así que en C++ podemos definir varias funciones con el mismo nombre, con la única condición de que el número y/o el tipo de los argumentos sean distintos. El compilador decide cual de las versiones de la función usará después de analizar el número y el tipo de los parámetros. Si ninguna de las funciones se adapta a los parámetros indicados, se aplicarán las reglas implícitas de conversión de tipos.

Las ventajas son más evidentes cuando debemos hacer las mismas operaciones con objetos de diferentes tipos o con distinto número de objetos. Hasta ahora habíamos usado macros para esto, pero no siempre es posible usarlas, y además las macros tienen la desventaja de que se expanden siempre, y son difíciles de diseñar para funciones complejas. Sin embargo las funciones serán ejecutadas mediante llamadas, y por lo tanto sólo habrá una copia de cada una.

Nota: Esta propiedad sólo existe en C++, no en C.

Ejemplo:

#include <iostream>
using namespace std;

int mayor(int a, int b);
char mayor(char a, char b);
double mayor(double a, double b);

int main() {
   cout << mayor('a', 'f') << endl;
   cout << mayor(15, 35) << endl;
   cout << mayor(10.254, 12.452) << endl;

   return 0;
}

int mayor(int a, int b) {
   if(a > b) return a; else return b;
}

char mayor(char a, char b) {
   if(a > b) return a; else return b;
}

double mayor(double a, double b) {
   if(a > b) return a; else return b;
}

Otro ejemplo:

#include <iostream>
using namespace std;

int mayor(int a, int b);
int mayor(int a, int b, int c);
int mayor(int a, int b, int c, int d);

int main() {
   cout << mayor(10, 4) << endl;
   cout << mayor(15, 35, 23) << endl;
   cout << mayor(10, 12, 12, 18) << endl;

   return 0;
}

int mayor(int a, int b) {
   if(a > b) return a; else return b;
}

int mayor(int a, int b, int c) {
   return mayor(mayor(a, b), c);
}

int mayor(int a, int b, int c, int d) {
   return mayor(mayor(a, b), mayor(c, d));
}

El primer ejemplo ilustra el uso de sobrecarga de funciones para operar con objetos de distinto tipo. El segundo muestra cómo se puede sobrecargar una función para operar con distinto número de objetos. Por supuesto, el segundo ejemplo se puede resolver también con parámetros por defecto.

Resolución de sobrecarga

Las llamadas a funciones sobrecargadas se resuelven en la fase de compilación. Es el compilador el que decide qué versión de la función debe ser invocada, después de analizar, y en ciertos casos, tratar los argumentos pasados en la llamadas.

A este proceso se le llama resolución de sobrecarga.

Hay que tener presente que el compilador puede aplicar conversiones de tipo, promociones o demociones, para que la llamada se ajuste a alguno de los prototipos de la función sobrecargada. Pero esto es algo que también sucede con las funciones no sobrecargadas, de modo que no debería sorprendernos.

En nuestro último ejemplo, una llamada como:

...
   cout << mayor('A', 'v', 'r') << endl;
...

Funcionaría igualmente bien, invocando a la versión de la función mayor con tres argumentos. El compilador hará la conversión implícita de char a int. Por supuesto, el valor retornado será un int, no un char.

Sin embargo, si usamos un valor double o float para alguno de los parámetros, obtendremos un aviso.

Problema

Propongo un ejercicio: implementar este segundo ejemplo usando parámetros por defecto. Para que sea más fácil, hacerlo sólo para parámetros con valores positivos, y si te sientes valiente, hazlo también para cualquier tipo de valor.