¿Cómo lograr la sobrecarga de funciones en C?

241

¿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 hay un camino directo; Estoy buscando soluciones si existen.

FL4SOF
fuente
66
Por qué querrías hacer esto? C no tiene habilidades polimórficas. Entonces foo (tipo aleatorio) es imposible. Simplemente haga funcs reales foo_i, foo_ch, foo_d, etc.
jmucchiello
44
Puede ir por el mal camino usando punteros vacíos y identificadores de tipo.
alk
11
Creo que debería llamar la atención sobre el hecho de que la respuesta a esta pregunta ha cambiado desde que se hizo originalmente , con el nuevo estándar C.
Leushenko

Respuestas:

127

Hay pocas posibilidades:

  1. funciones de estilo printf (escriba como argumento)
  2. funciones de estilo opengl (escriba el nombre de la función)
  3. subconjunto de c ++ (si puede usar un compilador de c ++)
Jacek Ławrynowicz
fuente
1
¿Puedes explicar o proporcionar enlaces para funciones de estilo opengl?
FL4SOF
1
@Lazer: Aquí hay una implementación simple de la función tipo printf.
Alexey Frunze
12
No. printf no está sobrecargando la función. utiliza vararg !!! Y C no admite la sobrecarga de funciones.
hqt
52
@hqt La respuesta nunca menciona la palabra sobrecarga.
kyrias
1
@kyrias Si la respuesta no es sobrecargar, está en la pregunta equivocada
Michael Mrozek
233

¡Si!

En el tiempo transcurrido desde que se hizo esta pregunta, el estándar C (sin extensiones) ha ganado efectivamente el soporte para la sobrecarga de funciones (no operadores), gracias a la adición de la _Genericpalabra clave en C11. (compatible con GCC desde la versión 4.9)

(La sobrecarga no está realmente "incorporada" de la manera que se muestra en la pregunta, pero es muy fácil implementar algo que funcione así).

_Generices un operador en tiempo de compilación en la misma familia que sizeofy _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 switchbloque. _Genericobtiene el tipo general de la expresión y luego "cambia" 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 controladora es int, por lo que elige la expresión asociada con intel valor. Nada de esto queda en tiempo de ejecución. (La defaultcláusula es opcional: si la deja desactivada y el tipo no coincide, provocará 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 cbrtoperación sobrecargada , enviando el tipo de argumento a la macro, eligiendo una función de implementación apropiada 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 puede usar foo(...)en su código sin preocuparse (mucho [1]) sobre el tipo de sus argumentos.


Para situaciones más complicadas, por ejemplo, funciones que sobrecargan un mayor número de argumentos o números variables, puede usar macros de utilidad para generar automáticamente estructuras de despacho 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 repeticiones para que se parezca a un lenguaje con soporte nativo para sobrecarga.

Por otro lado, ya era posible sobrecargar el número de argumentos (no el tipo) en C99.


[1] tenga en cuenta que la forma en que C evalúa los tipos podría tropezarlo. Esto elegirá foo_intsi intenta pasarle un literal de caracteres, por ejemplo, y necesita meterse un poco si desea que sus sobrecargas admitan literales de cadena. Sin embargo, en general sigue siendo bastante bueno.

Leushenko
fuente
Según su ejemplo, parece que lo único que se está sobrecargando es funcionar como macros. Déjame ver si entiendo correctamente: si quieres sobrecargar las funciones, solo estarías usando el preprocesador para desviar la llamada de función en función de los tipos de datos pasados, ¿verdad?
Nick
Por desgracia, cada vez que C11 comienza a captar, supongo que MISRA no aceptará esta característica por las mismas razones por las que prohíben listas de argumentos variables. Intento apegarme a MISRA muy cerca de mi mundo.
Nick
99
@ Nick, eso es todo sobrecarga. Simplemente se maneja implícitamente en otros idiomas (por ejemplo, realmente no se puede obtener "un puntero a una función sobrecargada" en ningún idioma, porque la sobrecarga implica múltiples cuerpos). Tenga en cuenta que esto no puede hacerlo solo el preprocesador, requiere un tipo de despacho de algún tipo; el preprocesador simplemente cambia su aspecto.
Leushenko
1
Como alguien que está bastante familiarizado con C99 y quiere aprender a hacer esto, esto parece demasiado complicado, incluso para C.
Tyler Crompton
55
@TylerCrompton Se evalúa en tiempo de compilación.
JAB
75

Como ya se dijo, la sobrecarga en el sentido de que quiere decir no es compatible con C. Un idioma común para resolver el problema es hacer que la función acepte una unión etiquetada . Esto se implementa mediante un structparámetro, donde el structmismo consiste en algún tipo de indicador de tipo, como an enum, y a unionde los diferentes tipos de valores. Ejemplo:

#include <stdio.h>

typedef enum {
    T_INT,
    T_FLOAT,
    T_CHAR,
} my_type;

typedef struct {
    my_type type;
    union {
        int a; 
        float b; 
        char c;
    } my_union;
} my_struct;

void set_overload (my_struct *whatever) 
{
    switch (whatever->type) 
    {
        case T_INT:
            whatever->my_union.a = 1;
            break;
        case T_FLOAT:
            whatever->my_union.b = 2.0;
            break;
        case T_CHAR:
            whatever->my_union.c = '3';
    }
}

void printf_overload (my_struct *whatever) {
    switch (whatever->type) 
    {
        case T_INT:
            printf("%d\n", whatever->my_union.a);
            break;
        case T_FLOAT:
            printf("%f\n", whatever->my_union.b);
            break;
        case T_CHAR:
            printf("%c\n", whatever->my_union.c);
            break;
    }

}

int main (int argc, char* argv[])
{
    my_struct s;

    s.type=T_INT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_FLOAT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_CHAR;
    set_overload(&s);
    printf_overload(&s); 
}
a2800276
fuente
22
¿Por qué no acaba de hacer todos los whatevers en funciones separadas ( set_int, set_float, etc.). Luego, "etiquetar con el tipo" se convierte en "agregar el nombre del tipo al nombre de la función". La versión en esta respuesta implica más tipeo, más costo de tiempo de ejecución, más posibilidades de errores que no se detectarán en el momento de la compilación ... ¡No veo ninguna ventaja para hacer las cosas de esta manera! ¿16 votos a favor?
Ben
20
Ben, esta respuesta está votada porque responde a la pregunta, en lugar de decir "no hagas eso". Tienes razón en que es más idiomático en C usar funciones separadas, pero si uno quiere polimorfismo en C, esta es una buena manera de hacerlo. Además, esta respuesta muestra cómo implementaría el polimorfismo en tiempo de ejecución en un compilador o VM: etiquete el valor con un tipo y luego envíe en función de eso. Por lo tanto, es una excelente respuesta a la pregunta original.
Nils von Barth
20

Este es el ejemplo más claro y conciso que he encontrado demostrando la sobrecarga de funciones en C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int addi(int a, int b) {
    return a + b;
}

char *adds(char *a, char *b) {
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)

int main(void) {
    int a = 1, b = 2;
    printf("%d\n", add(a, b)); // 3

    char *c = "hello ", *d = "world";
    printf("%s\n", add(c, d)); // hello world

    return 0;
}

https://gist.github.com/barosl/e0af4a92b2b8cabd05a7

Jay Taylor
fuente
1
Creo que este es un engaño de stackoverflow.com/a/25026358/1240268 en espíritu (pero con menos explicación).
Andy Hayden
1
Definitivamente prefiero 1 bloque continuo único de código completo y ejecutable al corte de corte y corte en cubitos que es # 1240268. A cada uno lo suyo.
Jay Taylor
1
Prefiero respuestas que expliquen lo que están haciendo y por qué funcionan. Esto tampoco. "Lo mejor que he visto hasta ahora" no es una exposición.
underscore_d
19

Si su compilador es gcc y no le importa hacer actualizaciones manuales cada vez que agrega una nueva sobrecarga, puede hacer un poco de macro magia y obtener el resultado que desea en términos de llamadas, no es tan bueno escribir ... pero es posible

mira __builtin_types_compatible_p, luego úsalo para definir una macro que haga algo como

#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

pero sí desagradable, simplemente no

EDITAR: C1X obtendrá soporte para expresiones genéricas de tipo que se ven así:

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)
Spudd86
fuente
13

Sí, más o menos.

Aquí tienes un ejemplo:

void printA(int a){
printf("Hello world from printA : %d\n",a);
}

void printB(const char *buff){
printf("Hello world from printB : %s\n",buff);
}

#define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 
#define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
#define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__) 
#define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) 
#define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
#define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) 
#define print(x , args ...) \
CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
({ \
if (__builtin_types_compatible_p (typeof (x), int)) \
printA(x, ##args); \
else \
printB (x,##args); \
})

int main(int argc, char** argv) {
    int a=0;
    print(a);
    print("hello");
    return (EXIT_SUCCESS);
}

Producirá 0 y hola ... desde printA e printB.

Capitán Barbosa
fuente
2
int main (int argc, char ** argv) {int a = 0; impresión (a); print ("hola"); volver (EXIT_SUCCESS); } generará 0 y hola ... desde printA e printB ...
Capitán Barbossa
1
__builtin_types_compatible_p, ¿no es ese compilador GCC específico?
Sogartar
11

El siguiente enfoque es similar al de a2800276 , pero con algo de magia macro C99 añadida:

// we need `size_t`
#include <stddef.h>

// argument types to accept
enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };

// a structure to hold an argument
struct sum_arg
{
    enum sum_arg_types type;
    union
    {
        long as_long;
        unsigned long as_ulong;
        double as_double;
    } value;
};

// determine an array's size
#define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))

// this is how our function will be called
#define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))

// create an array of `struct sum_arg`
#define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })

// create initializers for the arguments
#define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
#define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
#define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }

// our polymorphic function
long double _sum(size_t count, struct sum_arg * args)
{
    long double value = 0;

    for(size_t i = 0; i < count; ++i)
    {
        switch(args[i].type)
        {
            case SUM_LONG:
            value += args[i].value.as_long;
            break;

            case SUM_ULONG:
            value += args[i].value.as_ulong;
            break;

            case SUM_DOUBLE:
            value += args[i].value.as_double;
            break;
        }
    }

    return value;
}

// let's see if it works

#include <stdio.h>

int main()
{
    unsigned long foo = -1;
    long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
    printf("%Le\n", value);
    return 0;
}
Christoph
fuente
11

Puede que esto no ayude en absoluto, pero si está usando el sonido metálico puede usar el atributo sobrecargable: esto funciona incluso cuando se compila como C

http://clang.llvm.org/docs/AttributeReference.html#overloadable

Encabezamiento

extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));

Implementación

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
Steazy
fuente
10

En el sentido que quieres decir, no, no puedes.

Puedes declarar una va_argfunción como

void my_func(char* format, ...);

, pero deberá pasar algún tipo de información sobre el número de variables y sus tipos en el primer argumento, como lo printf()hace.

Quassnoi
fuente
6

Normalmente, una verruga para indicar que el tipo se agrega o se antepone al nombre. Puede salirse con la suya con algunas macros, pero depende de lo que intente hacer. No hay polimorfismo en C, solo coerción.

Se pueden realizar operaciones genéricas simples con macros:

#define max(x,y) ((x)>(y)?(x):(y))

Si su compilador admite typeof , se pueden poner operaciones más complicadas en la macro. Luego puede tener el símbolo foo (x) para admitir la misma operación en diferentes tipos, pero no puede variar el comportamiento entre diferentes sobrecargas. Si desea funciones reales en lugar de macros, es posible que pueda pegar el tipo en el nombre y usar un segundo pegado para acceder a él (no lo he probado).

Pete Kirkham
fuente
¿Puedes explicar un poco más sobre el enfoque basado en macro?
FL4SOF
4

La respuesta de Leushenko es realmente genial, únicamente: el fooejemplo no se compila con GCC, que falla al foo(7)tropezar con la FIRSTmacro y la llamada a la función real ( (_1, __VA_ARGS__), quedando con una coma excedente. Además, estamos en problemas si queremos proporcionar sobrecargas adicionales , tales como foo(double).

Así que decidí elaborar la respuesta un poco más, incluso para permitir una sobrecarga vacía ( foo(void)que causó algunos problemas ...).

La idea ahora es: ¡Defina más de un genérico en diferentes macros y deje seleccionar el correcto según el número de argumentos!

El número de argumentos es bastante fácil, basado en esta respuesta :

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

Eso es bueno, resolvemos uno SELECT_1o más SELECT_2(o más argumentos, si los quiere / necesita), por lo que simplemente necesitamos definiciones apropiadas:

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1),    \
        int: foo_int,                   \
        char: foo_char,                 \
        double: foo_double              \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

OK, ya agregué la sobrecarga vacía; sin embargo, esta no está cubierta por el estándar C, que no permite argumentos variados vacíos, es decir, ¡entonces confiamos en las extensiones del compilador !

Al principio, una llamada de macro vacía ( foo()) todavía produce un token, pero uno vacío. Por lo tanto, la macro de conteo en realidad devuelve 1 en lugar de 0 incluso en una llamada de macro vacía. Podemos "fácilmente" eliminar este problema, si colocamos la coma después de manera __VA_ARGS__ condicional , dependiendo de que la lista esté vacía o no:

#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

Eso parecía fácil, pero la COMMAmacro es bastante pesada; Afortunadamente, el tema ya está cubierto en un blog de Jens Gustedt (gracias, Jens). El truco básico es que las macros de funciones no se expanden si no se siguen entre paréntesis, para más explicaciones, eche un vistazo al blog de Jens ... Solo tenemos que modificar las macros un poco a nuestras necesidades (voy a usar nombres más cortos y menos argumentos para la brevedad).

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,

Y ahora estamos bien ...

El código completo en un bloque:

/*
 * demo.c
 *
 *  Created on: 2017-09-14
 *      Author: sboehler
 */

#include <stdio.h>

void foo_void(void)
{
    puts("void");
}
void foo_int(int c)
{
    printf("int: %d\n", c);
}
void foo_char(char c)
{
    printf("char: %c\n", c);
}
void foo_double(double c)
{
    printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
    printf("double: %.2f, int: %d\n", c, d);
}

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
        int: foo_int,                \
        char: foo_char,              \
        double: foo_double           \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N

#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,

int main(int argc, char** argv)
{
    foo();
    foo(7);
    foo(10.12);
    foo(12.10, 7);
    foo((char)'s');

    return 0;
}
Aconcagua
fuente
1

¿No puedes usar C ++ y no usar todas las demás funciones de C ++ excepto esta?

Si todavía no hay una C estricta, recomendaría funciones variadas .

Tim Matthews
fuente
3
No si un compilador de C ++ no está disponible para el sistema operativo para el que está codificando.
Brian
2
no solo eso, sino que podría querer un C ABI que no tenga nombre machacado.
Spudd86
-4

Espero que el siguiente código lo ayude a comprender la sobrecarga de funciones

#include <stdio.h>
#include<stdarg.h>

int fun(int a, ...);
int main(int argc, char *argv[]){
   fun(1,10);
   fun(2,"cquestionbank");
   return 0;
}
int fun(int a, ...){
  va_list vl;
  va_start(vl,a);

  if(a==1)
      printf("%d",va_arg(vl,int));
   else
      printf("\n%s",va_arg(vl,char *));
}
Madan Gopal
fuente
2
Una respuesta debería explicar qué está haciendo y por qué funciona. Si no es así, ¿cómo puede ayudar a alguien a entender algo?
underscore_d
No hay sobrecarga aquí.
melpomene
va_end nunca fue llamado
user2262111