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

13 minutos de lectura

avatar de usuario
FL4SOF

¿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 alternativas si existen.

  • Por qué querrías hacer esto? C no tiene habilidades polimórficas. Entonces foo (tipo aleatorio) es imposible. Solo haz funciones reales foo_i, foo_ch, foo_d, etc.

    – jmucchiello

    26 de enero de 2009 a las 13:10

  • Puedes ir por el mal camino usando punteros vacíos y tipos de identificación.

    – alk

    20 de octubre de 2011 a las 9:28

  • Siento que debo 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

    7 sep 2014 a las 0:05

avatar de usuario
Leushenko

¡Sí!

En el tiempo transcurrido desde que se hizo esta pregunta, el estándar C (sin extensiones) ha ganado soporte para sobrecarga de funciones (no operadores), gracias a la adición del _Generic palabra 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í).

_Generic es un operador de tiempo de compilación en 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 asociaciones de tipo/expresión que se parece un poco a una switch cuadra. _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 de control es intpor lo que elige la expresión asociada a int como el valor. Nada de esto permanece en tiempo de ejecución. (Él default La cláusula es opcional: si la deja desactivada 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 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 sobrecarga cbrt operación, despachando el tipo del argumento a la macro, eligiendo una función de implementación adecuada y luego pasando el argumento 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 un 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 usar foo(...) en tu código sin preocuparte (mucho[1]) sobre el tipo de sus argumentos.


Para situaciones más complicadas, por ejemplo, funciones que sobrecargan una gran cantidad de argumentos o números variables, puede usar macros de utilidad para generar automáticamente estructuras de envío 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 repetitivo para que se parezca mucho a un lenguaje con soporte nativo para la sobrecarga.

Aparte, 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 hacerle tropezar. esto elegirá foo_int si intenta pasarle un carácter literal, por ejemplo, y necesita perder un poco el tiempo si desea que sus sobrecargas admitan literales de cadena. Aún así, en general, bastante bien.

  • Según su ejemplo, parece que lo único que se sobrecarga es la función como macros. Déjame ver si entiendo correctamente: si deseas sobrecargar las funciones, solo estarías usando el preprocesador para desviar la llamada a la función en función de los tipos de datos pasados, ¿verdad?

    – Nick

    5 de febrero de 2015 a las 17:47


  • Por desgracia, cada vez que C11 comienza a ponerse de moda, supongo que MISRA no adoptará esta función por las mismas razones por las que prohíbe las listas de argumentos variables. Trato de mantenerme cerca de MISRA en mi mundo.

    – Nick

    5 de febrero de 2015 a las 17:50


  • @Nick eso es todo sobrecarga. Simplemente se maneja implícitamente en otros idiomas (por ejemplo, no se puede obtener “un puntero a una función sobrecargada” en ningún idioma, porque la sobrecarga implica varios cuerpos). Tenga en cuenta que esto no lo puede hacer el preprocesador solo, requiere un envío de tipo de algún tipo; el preprocesador simplemente cambia su apariencia.

    – Leushenko

    5 de febrero de 2015 a las 20:16

  • Como alguien que está bastante familiarizado con C99 y quiere aprender a hacerlo, parece demasiado complicado, incluso para C.

    -Tyler Crompton

    3 mayo 2016 a las 20:06


  • @TylerCrompton Se evalúa en tiempo de compilación.

    – JAB

    21 de abril de 2017 a las 14:45

Hay pocas posibilidades:

  1. funciones de estilo printf (escribir 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++)

  • ¿Puede explicar o proporcionar enlaces para funciones de estilo opengl?

    – FL4SOF

    26 de enero de 2009 a las 12:00

  • @Lazer: Aquí hay uno Implementación simple de funciones similares a printf.

    – Alexei Frunze

    20 de octubre de 2011 a las 10:34

  • No. printf no es una sobrecarga de funciones. usa vararg!!! Y C no admite la sobrecarga de funciones.

    – hqt

    29 de julio de 2012 a las 9:49

  • @hqt La respuesta nunca menciona la palabra sobrecarga.

    – remmy

    20 de junio de 2013 a las 16:12

  • @kyrias Si la respuesta no se trata de sobrecargar, se trata de la pregunta equivocada

    – Michael Mrozek

    11 mayo 2018 a las 21:54

avatar de usuario
a2800276

Como ya se indicó, la sobrecarga en el sentido que usted quiere decir no es compatible con C. Un modismo común para resolver el problema es hacer que la función acepte un unión etiquetada. Esto es implementado por un struct parámetro, donde struct consiste en algún tipo de indicador de tipo, como un enumy 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); 
}

  • ¿Por qué no harías todo el 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 escritura, 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 alguna ventaja en absoluto a hacer las cosas de esta manera! 16 votos a favor?!

    – ben

    29 de enero de 2013 a las 21:24

  • Ben, esta respuesta se votó porque responde la pregunta, en lugar de simplemente decir “no hagas eso”. Tiene 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íelo en función de eso. Por lo tanto, es una excelente respuesta a la pregunta original.

    – Nils von Barth

    8 de noviembre de 2014 a las 23:12


avatar de usuario
jay taylor

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

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

Puedes declarar un va_arg funcionar como

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

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

avatar de usuario
Aconcagua

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

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

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

El número de argumentos es bastante fácil, según 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 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     \
        )                               \
)

De acuerdo, ya agregué la sobrecarga de vacío; sin embargo, esta en realidad no está cubierta por el estándar C, que no permite argumentos variádicos vacíos, es decir, entonces confiar en las extensiones del compilador!

Al principio, una llamada de macro vacía (foo()) todavía produce un token, pero uno vacío. Entonces, 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 __VA_ARGS__ condicionalmentedependiendo de si la lista está vacía o no:

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

Ese mirado fácil, pero el COMMA macro es bastante pesado; afortunadamente, el tema ya está cubierto en un blog de Jens Gustedt (gracias, Jens). El truco básico es que las macros de función 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 un poco las macros según nuestras necesidades (voy a usar nombres más cortos y menos argumentos a favor de 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;
}

avatar de usuario
Cristóbal

El siguiente enfoque es similar a a2800276‘s, 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;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad