¿Cómo lograr la sobrecarga de funciones en C?
¿Hay alguna forma de lograr la sobrecarga de funciones en C? Estoy buscando funciones simples para sobrecargar como
foo (int a)
foo (char b)
foo (float c , int d)
Creo que no existe un camino sencillo; Estoy buscando soluciones alternativas, si existen.
¡Sí!
Desde que se hizo esta pregunta, el estándar C (sin extensiones) ha ganado efectivamente soporte para la sobrecarga de funciones (no operadores), gracias a la adición de la _Generic
palabra clave en C11. (compatible con GCC desde la versión 4.9)
(La sobrecarga no está realmente "integrada" como se muestra en la pregunta, pero es muy fácil implementar algo que funcione así).
_Generic
es un operador en tiempo de compilación de la misma familia que sizeof
y _Alignof
. Se describe en la sección estándar 6.5.1.1. Acepta dos parámetros principales: una expresión (que no se evaluará en tiempo de ejecución) y una lista de asociación de tipo/expresión que se parece un poco a un switch
bloque. _Generic
obtiene el tipo general de la expresión y luego lo "activa" para seleccionar la expresión del resultado final en la lista para su tipo:
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
La expresión anterior se evalúa como 2
: el tipo de expresión de control es int
, por lo que elige la expresión asociada int
como valor. Nada de esto permanece en tiempo de ejecución. (La default
cláusula es opcional: si la omite y el tipo no coincide, se producirá un error de compilación).
La forma en que esto es útil para la sobrecarga de funciones es que puede ser insertado por el preprocesador de C y elegir una expresión de resultado basada en el tipo de argumentos pasados a la macro de control. Entonces (ejemplo del estándar C):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Esta macro implementa una operación sobrecargada cbrt
, enviando el tipo de argumento a la macro, eligiendo una función de implementación adecuada y luego pasando el argumento de macro original a esa función.
Entonces, para implementar su ejemplo original, podríamos hacer esto:
foo_int (int a)
foo_char (char b)
foo_float_int (float c , int d)
#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__,)), \
int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A
En este caso podríamos haber usado una default:
asociación para el tercer caso, pero eso no demuestra cómo extender el principio a múltiples argumentos. El resultado final es que puedes usarlo foo(...)
en tu código sin preocuparte (mucho[1]) por el tipo de sus argumentos.
EDITAR Cosinus tiene una solución mucho más elegante para sobrecargas de múltiples argumentos que funciona con extensiones C23 o GNU ; la siguiente técnica se escribió contra C11 (que realmente no quería permitirle hacer esto).
Para situaciones más complicadas, por ejemplo, funciones que sobrecargan un mayor número de argumentos o números variables, puede utilizar macros de utilidad para generar automáticamente estructuras de distribución estáticas:
void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }
#define print(...) OVERLOAD(print, (__VA_ARGS__), \
(print_ii, (int, int)), \
(print_di, (double, int)), \
(print_iii, (int, int, int)) \
)
#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"
int main(void) {
print(44, 47); // prints "int, int"
print(4.4, 47); // prints "double, int"
print(1, 2, 3); // prints "int, int, int"
print(""); // prints "unknown arguments"
}
( implementación aquí ) Entonces, con un poco de esfuerzo, puede reducir la cantidad de texto repetitivo para que se parezca mucho a un lenguaje con soporte nativo para la sobrecarga.
Además, ya era posible sobrecargar la cantidad de argumentos (no el tipo) en C99.
[1] Sin embargo, tenga en cuenta que la forma en que C evalúa los tipos podría hacerle tropezar. Esto elegirá foo_int
si intentas pasarle un carácter literal, por ejemplo, y necesitas complicarte un poco si quieres que tus sobrecargas admitan literales de cadena. Aunque en general sigue siendo bastante bueno.
Hay algunas posibilidades:
printf
funciones de estilo (escriba como argumento);- Funciones de estilo OpenGL (escriba el nombre de la función);
- Subconjunto C de C++ (si puede utilizar un compilador de C++).