Sobrecarga de funciones y argumentos por defecto en C++

La sobrecarga de funciones nos permite definir múltiples funciones con el mismo nombre, siempre que sus firmas —es decir, su lista de parámetros (número, tipo o orden)— sean distintas. Por otro lado, los argumentos por defecto permiten que una función pueda ser llamada con menos argumentos de los que declara, asignando automáticamente un valor predefinido a los omitidos.

Para entender por qué esto es así, debemos mirar el proceso de resolución de sobrecarga (overload resolution). Cuando el compilador encuentra una llamada a una función sobrecargada, busca la “mejor coincidencia” (best match) comparando los tipos de los argumentos pasados con los parámetros de las funciones candidatas. El proceso sigue una jerarquía: primero busca una coincidencia exacta, luego intenta realizar una promoción (como de char a int), luego una conversión estándar (como de int a double) y, finalmente, una conversión definida por el usuario. Un detalle crítico es que el tipo de retorno no forma parte de la firma de la función; si intentas sobrecargar una función cambiando solo su retorno, el compilador no podrá distinguir la llamada y lanzará un error.

Esta técnica se utiliza para crear interfaces más limpias y semánticamente coherentes, donde una misma acción (como imprimir o guardar) puede aplicarse a diferentes tipos de datos sin cambiar el nombre de la operación. Sin embargo, debes tener cuidado: si la lógica de selección se vuelve ambigua (cuando dos funciones son igualmente buenas candidatos para una llamada), el compilador se detendrá y lanzará un error de ambigüedad.

#include <iostream>
#include <string_view>
#include <string>

// Demostración de una conversión definida por el usuario para testear la resolución
struct SensorData {
    int valor;
    operator int() const { return valor; }
};

class MonitorSystem {
public:
    // Sobrecarga basada en tipos: el compilador elige según el tipo de argumento.
    // Se utiliza std::string_view [C++17] para evitar copias innecesarias de strings.
    void log(int code) const {
        std::cout << "[INFO] Código de estado: " << code << "\n";
    }

    void log(std::string_view message) const {
        std::cout << "[LOG]: " << message << "\n";
    }

    // Argumentos por defecto: deben declararse al final de la lista de parámetros.
    // En un entorno real, esto se definiría solo en el header (.h).
    void configure(std::string_view mode, bool verbose = false) const {
        std::cout << "Modo: " << mode << (verbose ? " (detallado)" : " (silencioso)") << "\n";
    }

    // [[nodiscard]] [C++17]: Avisa si el programador ignora el valor de retorno.
    [[nodiscard]] int calculate_checksum(int data) const {
        return data ^ 0xDEAD;
    }
};

// inline: Sugerencia al compilador para expandir la función en el punto de llamada.
// Es vital para evitar violaciones de la ODR (One Definition Rule) si se define en un header.
inline int fast_sum(int a, int b) {
    return a + b;
}

int main() {
    MonitorSystem monitor;

    // Resolución de sobrecarga: el compilador identifica el tipo exacto.
    monitor.log(200);                  // Llama a log(int)
    monitor.log("Sistema operativo");  // Llama a log(std::string_view)

    // Uso de argumentos por defecto: se puede omitir el último parámetro.
    monitor.configure("Producción");              // Usa el default (false)
    monitor.configure("Depuración", true);        // Sobrescribe el default

    // Uso de [[nodiscard]]: el compilador emitirá un warning si no guardamos el resultado.
    int checksum = monitor.calculate_checksum(1024);
    if (checksum != 0) {
        std::cout << "Checksum calculado: " << checksum << "\n";
    }

    // Función inline para evitar el overhead de la llamada de función.
    std::cout << "Suma rápida: " << fast_sum(15, 30) << "\n";

    return 0;
}

Para compilar este ejemplo:
g++ -std=c++20 -Wall -Wextra -Wpedantic -o monitor monitor.cpp

Desglose del código

  • monitor.log(200) vs monitor.log("..."): Aquí el compilador realiza la resolución de sobrecarga. Para el primer caso, encuentra una coincidencia exacta con int. Para el segundo, la literal de cadena se convierte a std::string_view [C++17], lo cual es una conversión estándar permitida.
  • monitor.configure(std::string_view, bool = false): Implementamos un argumento por defecto en el parámetro verbose. Esto permite que monitor.configure("Producción") sea una llamada válida, donde el compilador inserta el valor false internamente. Nota que si intentáramos poner void configure(int x = 0, int y) (con un parámetro obligatorio después de uno opcional), el compilador lanzaría un error sintáctico.
  • [[nodiscard]]: Si llamaras a monitor.calculate_checksum(1024); sin asignar el resultado a una variable, el compilador (con flags de advertencia activados) te avisaría. Esto es fundamental en funciones que devuelven códigos de error o valores críticos.
  • inline fast_sum: Al marcarla como inline, le indicamos al compilador que el costo de la llamada (saltar a la dirección de memoria, limpiar el stack, etc.) es mayor que el beneficio de la función misma, sugiriendo que el cuerpo de la función sea insertado directamente donde se llama.
  • SensorData: Aunque no se usa para una llamada ambigua en el main para mantener el código compilable, su presencia es un recordatorio: si tuviéramos log(int) y log(double) y pasáramos un objeto SensorData que puede convertirse a ambos, el compilador fallaría por ambigüedad al no poder decidir cuál es la “mejor” conversión.

El error frecuente

Un error clásico ocurre al mezclar sobrecarga y argumentos por defecto de forma que se cree una llamada ambigua.

void procesar(int x) { /* ... */ }
void procesar(int x, int y = 0) { /* ... */ }

// Error de compilación:
procesar(42); 

En este caso, cuando llamas a procesar(42), el compilador encuentra dos candidatos:
1. procesar(int): Coincidencia exacta.
2. procesar(int, int): Coincidencia exacta si asumimos el valor por defecto para el segundo parámetro.

Ambas son igualmente buenas para el compilador, lo que resulta en una ambigüedad de sobrecarga. El compilador no “adivina” que quieres la versión de un solo parámetro; simplemente se detiene. Para evitarlo, mantén las firmas de tus funciones lo más distintas posible o evita combinar sobrecarga con valores por defecto en parámetros que puedan confundirse.

33

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio