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


¿Hay alguna manera de lograr la sobrecarga de funciones en C? Estoy mirando funciones simples para ser sobrecargado como

foo (int a)  
foo (char b)  
foo (float c , int d)

Creo que no hay una manera directa; estoy buscando soluciones si existe alguna.

Author: Gaurang Tandon, 2009-01-26

14 answers

Hay pocas posibilidades:

  1. funciones de estilo printf (escriba como argumento)
  2. funciones de estilo opengl (escriba el nombre de la función)
  3. c subconjunto de c++ (si puede usar un compilador de c++)
 113
Author: Jacek Ławrynowicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-26 09:24:41

Sí!

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

(La sobrecarga no es realmente "incorporada" en la moda mostrada 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 como 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 será evaluada en tiempo de ejecución), y una lista de asociaciones de tipo/expresión que se parece un poco a un bloque switch. _Generic obtiene el tipo general de la expresión y luego "cambia" para seleccionar la expresión de 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 la expresión controladora es int, por lo que elige expresión asociada con int como valor. Nada de esto permanece en tiempo de ejecución. (La cláusula default es opcional: si la deja desactivada y el tipo no coincide, causará un error de compilación.)

La forma en que esto es útil para la sobrecarga de funciones es que puede ser insertada por el preprocesador C y elegir una expresión de resultado basada en el tipo de los argumentos pasados a la macro controladora. Así (ejemplo del estándar C):

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

Esta macro implementa una sobrecarga cbrt operación, enviando el tipo del argumento a la macro, eligiendo una función de implementación apropiada, y luego pasando el argumento de la macro original a esa función.

Así que 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 asociación default: 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 preocupándose (mucho[1]) por 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í ) Así que con un poco de esfuerzo, puede reducir la cantidad de repeticiones a parecerse más o menos a un lenguaje con soporte nativo para sobrecarga.

Como un aparte, fue ya es 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 hacerle tropezar. Esto elegirá foo_int si intenta pasarle un carácter literal, por ejemplo, y necesita desordenar un poco si desea que sus sobrecargas soporten literales de cadena. Sin embargo, en general sigue siendo bastante genial.

 190
Author: Leushenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:31

Como ya se ha dicho, la sobrecarga en el sentido que quiere decir no está soportada por C. Un modismo común para resolver el problema es hacer que la función acepte una unión etiquetada. Esto es implementado por un parámetro struct, donde el struct en sí consiste en algún tipo de indicador de tipo, como un enum, y un union de 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); 
}
 71
Author: a2800276,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-09 00:30:56

Si su compilador es gcc y no le importa hacer actualizaciones manuales cada vez que agrega una nueva sobrecarga, puede hacer algo de magia macro y obtener el resultado que desea en términos de llamadores, no es tan agradable 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

EDIT: C1X recibirá soporte para expresiones genéricas de tipo se ven así:

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)
 19
Author: Spudd86,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-13 21:13:51

Aquí está el ejemplo más claro y conciso que he encontrado demostrando 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

 16
Author: Jay Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-21 15:54:46

Sí, más o menos.

Aquí tienes el 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);
}

Mostrará 0 y hola.. de printA y printB.

 13
Author: Nautical,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-08 07:41:24

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

// 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;
}
 11
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-26 10:25:46

Esto puede no ayudar en absoluto, pero si está usando clang puede usar el atributo overloadable - Esto funciona incluso cuando se compila como C

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

Cabecera

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

Aplicación

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
 11
Author: Steazy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-11 08:52:58

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

Puede declarar una función va_arg como

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

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

 9
Author: Quassnoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-26 09:25:26

Normalmente una verruga para indicar el tipo se añade o antepone al nombre. Puede salirse con la suya con macros es algunos casos, pero más bien depende de lo que está tratando de hacer. No hay polimorfismo en C, sólo coerción.

Las operaciones genéricas simples se pueden hacer con macros:

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

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

 6
Author: Pete Kirkham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-28 10:10:33

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

Así que decidí elaborar la respuesta un poco más, incluyendo permitir una sobrecarga de vacío (foo(void) – que causó bastantes problemas...).

La idea ahora es: Definir más de un genérico en diferentes macros y dejar seleccionar el correcto de acuerdo con 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 ya sea SELECT_1 o SELECT_2 (o más argumentos, si los quieres / los necesitas), 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, he añadido la sobrecarga de vacío ya-sin embargo, este en realidad no está cubierto por el estándar C, que no permite argumentos variádicos vacíos, es decir, entonces confiamos en las extensiones del compilador!

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

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

Que parecía fácil, pero la macro COMMA 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 van seguidas de paréntesis, para más explicaciones, echa 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;
}
 3
Author: Aconcagua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-14 15:13:48

¿No puedes simplemente usar C++ y no usar todas las demás características de C++ excepto esta?

Si todavía no hay una C estricta, entonces recomendaría funciones variádicas en su lugar.

 1
Author: Tim Matthews,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-26 09:30:10

Intenta declarar estas funciones como {[0] } si tu compilador soporta esto, http://msdn.microsoft.com/en-us/library/s6y4zxec (VS.80).aspx

 -3
Author: dmityugov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-26 10:45:51

Espero que el siguiente código le ayude a entender 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 *));
}
 -3
Author: Madan Gopal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 04:50:31