Come ottenere il sovraccarico della funzione in C?


241

Esiste un modo per ottenere il sovraccarico della funzione in C? Sto guardando funzioni semplici da sovraccaricare come

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

Penso che non esista un modo diretto; Sto cercando soluzioni alternative se ne esistono.


6
Perché vorresti farlo? C non ha abilità polimorfiche. Quindi il foo (tipo casuale) è impossibile. Basta creare funzioni reali foo_i, foo_ch, foo_d, ecc.
jmucchiello,

4
Puoi andare nella direzione malvagia usando i puntatori vuoti e digitare id.
alk

11
Sento che dovrei attirare l'attenzione sul fatto che la risposta a questa domanda è cambiata da quando è stata originariamente posta , con il nuovo standard C.
Leushenko,

Risposte:


127

Ci sono poche possibilità:

  1. funzioni di stile printf (digitare come argomento)
  2. funzioni di stile opengl (digitare il nome della funzione)
  3. sottoinsieme c di c ++ (se è possibile utilizzare un compilatore c ++)

1
puoi spiegare o fornire link per funzioni in stile opengl?
FL4SOF,

1
@Lazer: ecco una semplice implementazione di funzioni simili a printf.
Alexey Frunze,

12
No. printf non ha un sovraccarico di funzioni. usa vararg !!! E C non supporta il sovraccarico delle funzioni.
hqt

52
@hqt La risposta non menziona mai la parola sovraccarico.
kyrias,

1
@kyrias Se la risposta non riguarda il sovraccarico è sulla domanda sbagliata
Michael Mrozek,

233

Sì!

Nel tempo da quando questa domanda è stata posta, lo standard C (senza estensioni) ha effettivamente ottenuto il supporto per il sovraccarico delle funzioni (non per gli operatori), grazie all'aggiunta della _Genericparola chiave in C11. (supportato in GCC dalla versione 4.9)

(Il sovraccarico non è veramente "integrato" nel modo mostrato nella domanda, ma è facile implementare qualcosa che funzioni in questo modo.)

_Genericè un operatore in fase di compilazione nella stessa famiglia di sizeofe _Alignof. È descritto nella sezione standard 6.5.1.1. Accetta due parametri principali: un'espressione (che non verrà valutata in fase di esecuzione) e un elenco di associazioni tipo / espressione che assomiglia un po 'a un switchblocco. _Genericottiene il tipo generale dell'espressione e quindi "passa" su di esso per selezionare l'espressione del risultato finale nell'elenco per il suo tipo:

_Generic(1, float: 2.0,
            char *: "2",
            int: 2,
            default: get_two_object());

L'espressione sopra restituisce 2: il tipo dell'espressione di controllo è int, quindi sceglie l'espressione associata intcome valore. Nulla di tutto questo rimane in fase di esecuzione. (La defaultclausola è facoltativa: se la lasci disattivata e il tipo non corrisponde, si verificherà un errore di compilazione.)

Il modo in cui ciò è utile per il sovraccarico della funzione è che può essere inserito dal preprocessore C e scegliere un'espressione di risultato in base al tipo di argomenti passati alla macro di controllo. Quindi (esempio dallo standard C):

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

Questa macro implementa un'operazione sovraccarica cbrt, inviando il tipo di argomento alla macro, scegliendo una funzione di implementazione appropriata e quindi passando l'argomento macro originale a quella funzione.

Quindi, per implementare il tuo esempio originale, potremmo farlo:

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

In questo caso avremmo potuto usare default:un'associazione per il terzo caso, ma ciò non dimostra come estendere il principio a più argomenti. Il risultato finale è che puoi usare il foo(...)tuo codice senza preoccuparti (molto [1]) del tipo dei suoi argomenti.


Per situazioni più complicate, ad esempio funzioni che sovraccaricano un numero maggiore di argomenti o numeri diversi, è possibile utilizzare le macro di utilità per generare automaticamente strutture di invio statico:

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"
}

( implementazione qui ) Quindi, con un certo sforzo, è possibile ridurre la quantità di boilerplate in modo che assomigli a un linguaggio con supporto nativo per il sovraccarico.

A parte questo, era già possibile sovraccaricare il numero di argomenti (non il tipo) in C99.


[1] nota che il modo in cui C valuta i tipi potrebbe farti inciampare. Questo sceglierà foo_intse provi a passargli un carattere letterale, per esempio, e devi fare un po 'di casino se vuoi che i tuoi sovraccarichi supportino i letterali di stringa. Comunque, nel complesso, è piuttosto bello.


Sulla base del tuo esempio sembra che l'unica cosa che viene sovraccaricata sia la funzione come le macro. Fammi vedere se capisco correttamente: se desideri sovraccaricare le funzioni, utilizzeresti semplicemente il preprocessore per deviare la chiamata di funzione in base ai tipi di dati trasmessi, giusto?
Nick,

Purtroppo, ogni volta che C11 inizia a prendere piede, suppongo che MISRA non abbraccerà questa funzione per le stesse ragioni per cui proibiscono elenchi di argomenti variabili. Provo a rimanere vicino a MISRA abbastanza vicino nel mio mondo.
Nick,

9
@Nick è tutto sovraccarico. È gestito in modo implicito in altre lingue (ad esempio, non è possibile ottenere "un puntatore a una funzione sovraccarica" ​​in qualsiasi lingua, poiché il sovraccarico implica più corpi). Si noti che questo non può essere fatto solo dal preprocessore, richiede un tipo di invio del tipo; il preprocessore cambia solo l'aspetto.
Leushenko,

1
Come qualcuno che ha abbastanza familiarità con C99 e vuole imparare come farlo, questo sembra eccessivamente complicato, anche per C.
Tyler Crompton,

5
@TylerCrompton Viene valutato in fase di compilazione.
JAB

75

Come già detto, il sovraccarico nel senso che intendi non è supportato da C. Un linguaggio comune per risolvere il problema sta facendo accettare alla funzione un'unione contrassegnata . Questo è implementato da un structparametro, in cui lo structstesso è costituito da una sorta di indicatore di tipo, come un enume uno uniondei diversi tipi di valori. Esempio:

#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); 
}

22
Perché non si solo fare tutte le whatevers in funzioni separate ( set_int, set_float, ecc). Quindi "taggando con il tipo" diventa "aggiungi il nome del tipo al nome della funzione". La versione in questa risposta implica più digitazione, più costi di runtime, più possibilità di errori che non verranno colti al momento della compilazione ... Non vedo alcun vantaggio nel fare le cose in questo modo! 16 voti ?!
Ben

20
Ben, questa risposta è annullata perché risponde alla domanda, invece di dire semplicemente "non farlo". Hai ragione sul fatto che in C è più idiomatico usare funzioni separate, ma se si vuole il polimorfismo in C, questo è un buon modo per farlo. Inoltre, questa risposta mostra come implementare il polimorfismo di runtime in un compilatore o VM: taggare il valore con un tipo e quindi inviarlo in base a quello. È quindi un'ottima risposta alla domanda originale.
Nils von Barth,

20

Ecco l'esempio più chiaro e conciso che ho trovato dimostrando il sovraccarico della funzione in 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


1
Penso che sia uno scherzo di stackoverflow.com/a/25026358/1240268 nello spirito (ma con meno spiegazioni).
Andy Hayden,

1
Preferisco sicuramente 1 singolo blocco continuo di codice completo ed eseguibile al taglio di taglio e taglio che è # 1240268. A ognuno il suo.
Jay Taylor,

1
Preferisco le risposte che spiegano cosa stanno facendo e perché funzionano. Questo non fa nessuno dei due. "Il migliore che abbia mai visto:" non è un'esposizione.
underscore_d

19

Se il tuo compilatore è gcc e non ti dispiace fare aggiornamenti manuali ogni volta che aggiungi un nuovo sovraccarico puoi fare un po 'di macro magia e ottenere il risultato desiderato in termini di chiamanti, non è così bello scrivere ... ma è possibile

guarda __builtin_types_compatible_p, quindi usalo per definire una macro che fa qualcosa di simile

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

ma sì, solo che no

EDIT: C1X riceverà supporto per le espressioni di tipo generico che assomigliano a questo:

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

13

Sì, una specie di.

Ecco l'esempio:

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);
}

Verrà emesso 0 e ciao .. da printA e printB.


2
int main (int argc, char ** argv) {int a = 0; stampare (a); stampare ( "ciao"); return (EXIT_SUCCESS); } produrrà 0 e ciao .. da printA e printB ...
Capitano Barbossa,

1
__builtin_types_compatible_p, non è specifico del compilatore GCC?
Sogartar,

11

Il seguente approccio è simile a quello di a2800276 , ma con l'aggiunta di alcune macro magie C99:

// 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

Questo potrebbe non essere affatto d'aiuto, ma se stai usando clang puoi usare l'attributo overload - Funziona anche durante la compilazione come C

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

Intestazione

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

Implementazione

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

10

Nel senso che intendi - no, non puoi.

Puoi dichiarare una va_argfunzione come

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

, ma nel primo argomento dovrai passare alcune informazioni sul numero di variabili e sui loro tipi, come printf()fa.


6

Normalmente una verruca per indicare il tipo viene aggiunta o anteposta al nome. Puoi cavartela con le macro in alcuni casi, ma dipende piuttosto da cosa stai cercando di fare. Non c'è polimorfismo in C, solo coercizione.

È possibile eseguire semplici operazioni generiche con le macro:

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

Se il compilatore supporta typeof , nella macro è possibile inserire operazioni più complesse. È quindi possibile avere il simbolo foo (x) per supportare la stessa operazione di tipi diversi, ma non è possibile variare il comportamento tra sovraccarichi diversi. Se desideri funzioni effettive anziché macro, potresti essere in grado di incollare il tipo sul nome e utilizzare un secondo incolla per accedervi (non ho provato).


puoi spiegare un po 'di più sull'approccio basato su macro.
FL4SOF,

4

La risposta di Leushenko è davvero interessante - solo: l' fooesempio non si completa con GCC, che fallisce foo(7), inciampando sulla FIRSTmacro e la chiamata effettiva della funzione ( (_1, __VA_ARGS__), rimanendo con una virgola in eccesso. Inoltre, siamo nei guai se vogliamo fornire sovraccarichi aggiuntivi , come ad esempio foo(double).

Così ho deciso di elaborare un po 'di più la risposta, anche per consentire un sovraccarico vuoto ( foo(void)- che ha causato alcuni problemi ...).

L'idea ora è: definire più di un generico in diverse macro e lasciare selezionare quello corretto in base al numero di argomenti!

Il numero di argomenti è abbastanza semplice, basato su questa risposta :

#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

È carino, risolviamo uno SELECT_1o SELECT_2(o più argomenti, se vuoi / ne hai bisogno), quindi abbiamo semplicemente bisogno di definizioni appropriate:

#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, ho già aggiunto il sovraccarico del vuoto - tuttavia, questo in realtà non è coperto dallo standard C, che non consente argomenti variadici vuoti, quindi facciamo affidamento sulle estensioni del compilatore !

All'inizio, una chiamata a macroistruzione vuota ( foo()) produce ancora un token, ma uno vuoto. Quindi la macro di conteggio in realtà restituisce 1 anziché 0 anche in caso di chiamata a macroistruzione vuota. Possiamo "facilmente" eliminare questo problema, se posizioniamo la virgola dopo in modo __VA_ARGS__ condizionale , a seconda che l'elenco sia vuoto o meno:

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

Che sembrava facile, ma la COMMAmacro è piuttosto pesante uno; fortunatamente, l'argomento è già trattato in un blog di Jens Gustedt (grazie, Jens). Il trucco di base è che le macro di funzioni non vengono espanse se non seguite da parentesi, per ulteriori spiegazioni, dai un'occhiata al blog di Jens ... Dobbiamo solo modificarle un po 'in base alle nostre esigenze (userò nomi più brevi e meno argomenti per brevità).

#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 ,

E ora stiamo bene ...

Il codice completo in un blocco:

/*
 * 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;
}

1

Non puoi semplicemente usare C ++ e non usare tutte le altre funzionalità C ++ tranne questa?

Se non fosse ancora solo una C rigorosa, raccomanderei invece le funzioni variadiche .


3
Non se un compilatore C ++ non è disponibile per il sistema operativo per cui sta codificando.
Brian,

2
non solo, ma potrebbe desiderare un ABI C che non abbia il nome che vi si rovina.
Spudd86,


-4

Spero che il codice seguente ti aiuti a capire il sovraccarico delle funzioni

#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 *));
}

2
Una risposta dovrebbe spiegare cosa sta facendo e perché funziona. In caso contrario, come può aiutare chiunque a capire qualcosa?
underscore_d

Non c'è sovraccarico qui.
melpomene,

va_end non è mai stato chiamato
user2262111
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.