Jak osiągnąć przeciążenie funkcji w C?

Czy Jest jakiś sposób na osiągnięcie przeciążenia funkcji w C? Szukam prostych funkcji do przeciążenia jak

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

Myślę, że nie ma prostej drogi do przodu; Szukam obejść, jeśli istnieją.

Author: Gaurang Tandon, 2009-01-26

14 answers

Istnieje kilka możliwości:

  1. Funkcje stylu printf (Typ jako argument)
  2. Funkcje w stylu opengl (wpisz nazwę funkcji)
  3. C podzbiór c++ (jeśli możesz użyć kompilatora 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/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 09:24:41

Tak!

W czasie, gdy to pytanie zostało zadane, standard C (no extensions) skutecznie zyskał wsparcie dla przeciążania funkcji (nie operatorów), dzięki dodaniu słowa kluczowego _Generic w C11. (obsługiwane w GCC od wersji 4.9)

(przeciążenie nie jest tak naprawdę "wbudowane" w sposób pokazany w pytaniu, ale bardzo łatwo jest zaimplementować coś, co działa w ten sposób.)

_Generic jest operatorem czasu kompilacji w tej samej rodzinie jako sizeof i _Alignof. Jest to opisane w normalnym punkcie 6.5.1.1. Przyjmuje dwa główne parametry: wyrażenie (które nie będzie oceniane w trybie runtime) oraz listę asocjacji typu/wyrażenia, która wygląda trochę jak blok switch. _Generic pobiera ogólny typ wyrażenia, a następnie "włącza" je, aby wybrać wyrażenie wyniku końcowego na liście dla jego typu:

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

Powyższe wyrażenie ewaluuje do 2 - typem wyrażenia sterującego jest int, więc wybiera wyrażenie powiązane z int jako wartością. Nic z tego nie pozostaje w czasie wykonywania. (Klauzula default jest opcjonalna: jeśli zostawisz ją wyłączoną i typ nie pasuje, spowoduje to błąd kompilacji.)

Sposób, w jaki jest to przydatne do przeciążania funkcji polega na tym, że preprocesor C może ją wstawić i wybrać wyrażenie wynikowe na podstawie typu argumentów przekazywanych do kontrolującego makra. Tak (przykład ze standardu C):

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

To makro implementuje przeciążony cbrt operacja polega na wysłaniu do makra typu argumentu, wybraniu odpowiedniej funkcji implementacyjnej, a następnie przekazaniu oryginalnego argumentu makra do tej funkcji.

Aby zaimplementować twój oryginalny przykład, możemy zrobić to:]}
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

W tym przypadku mogliśmy użyć skojarzenia default: dla trzeciego przypadku, ale to nie pokazuje, jak rozszerzyć zasadę na wiele argumentów. Efektem końcowym jest to, że możesz użyć foo(...) w swoim kodzie bez niepokojące (dużo [1]) o rodzaj swoich argumentów.


W bardziej skomplikowanych sytuacjach, np. funkcje przeciążające większą liczbę argumentów lub różne liczby, można użyć makr narzędziowych do automatycznego generowania statycznych struktur dyspozytorskich:

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

(implementacja tutaj ), więc przy pewnym wysiłku, możesz zmniejszyć ilość boilerplate do wyglądającego prawie jak język z natywną obsługą przeciążania.

Na marginesie, to było już możliwe przeciążenie liczby argumentów (Nie typu) w C99.


[1] zauważ, że sposób, w jaki C ocenia typy, może cię potknąć. To wybierze foo_int, jeśli spróbujesz przekazać mu literał znaków, na przykład i musisz trochę namieszać , jeśli chcesz, aby przeciążenia obsługiwały literały łańcuchów. Ale ogólnie całkiem fajne.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:31

Jak już wspomniano, przeciążenie w tym sensie, że masz na myśli, że nie jest obsługiwane przez C. powszechnym idiomem do rozwiązania problemu jest zmuszenie funkcji do przyjęcia tagged union . Jest to zaimplementowane przez parametr struct, gdzie sam struct składa się z pewnego rodzaju wskaźnika typu, takiego jak enum i union różnych typów wartości. Przykład:

#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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-09 00:30:56

Jeśli twoim kompilatorem jest gcc i nie masz nic przeciwko ręcznym aktualizacjom za każdym razem, gdy dodajesz nowe przeciążenie, możesz zrobić magię makr i uzyskać pożądany wynik pod względem wywoływaczy, nie jest tak miło pisać... ale to możliwe

Spójrz na _ _ builtin _ types _ compatible _ p, a następnie użyj go do zdefiniowania makra, które robi coś w stylu

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

But yea nasty, just don ' t

EDIT: C1X otrzyma wsparcie dla typów wyrażeń generycznych, które wyglądają tak:

#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/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-13 21:13:51

Oto najjaśniejszy i najbardziej zwięzły przykład, jaki znalazłem demonstrując przeciążenie funkcji w 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/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 15:54:46

Tak jakby.

Oto przykład:

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

Wyświetli 0 i hello .. od printA i 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-08 07:41:24

Poniższe podejście jest podobne do a2800276 , ale z dodaną magią makro 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
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 10:25:46

Może to w ogóle nie pomóc, ale jeśli używasz clang, możesz użyć atrybutu overloadable - działa to nawet podczas kompilacji jako C

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

Header

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

Realizacja

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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-11 08:52:58

W sensie, który masz na myśli - Nie, Nie możesz.

Możesz zadeklarować va_arg funkcję jak

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

, ale musisz podać jakieś informacje o liczbie zmiennych i ich typach w pierwszym argumencie - tak jak robi to 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/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 09:25:26

Zwykle brodawka wskazująca typ jest dołączana lub poprzedzana nazwą. Możesz uciec od makr w niektórych przypadkach, ale to raczej zależy od tego, co próbujesz zrobić. W C nie ma polimorfizmu, tylko przymus.

Proste operacje generyczne można wykonywać za pomocą makr:

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

Jeśli twój kompilator obsługuje typeof , w makrze można umieścić bardziej skomplikowane operacje. Możesz wtedy mieć symbol foo (x), aby obsługiwać tę samą operację różnych typów, ale nie możesz zmieniać zachowanie między różnymi przeciążeniami. Jeśli chcesz mieć rzeczywiste funkcje, a nie makra, możesz być w stanie wkleić typ do nazwy i użyć drugiego wklejenia, aby uzyskać do niego dostęp(nie próbowałem).

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-28 10:10:33

Odpowiedź Leushenki jest naprawdę fajna-wyłącznie: przykład foo nie kompiluje się z GCC, który zawodzi przy foo(7), potykając się o makro FIRST i rzeczywiste wywołanie funkcji ((_1, __VA_ARGS__), pozostając z nadmiarem przecinka. Dodatkowo, mamy kłopoty, jeśli chcemy zapewnić dodatkowe przeciążenia, takie jak foo(double).

Więc postanowiłem rozwinąć odpowiedź nieco dalej, w tym pozwolić na przeciążenie void (foo(void) - co spowodowało sporo kłopotów...).

Idea teraz jest: Zdefiniuj więcej niż jeden rodzajnik w różnych makrach i wybierz poprawne zgodnie z liczbą argumentów!

Liczba argumentów jest dość prosta, bazując na tej odpowiedzi :

#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

To miłe, decydujemy się na SELECT_1 lub SELECT_2 (lub więcej argumentów, jeśli chcesz/potrzebujesz ich), więc po prostu potrzebujemy odpowiednich definicji:

#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, dodałem już przeciążenie void-jednak ten w rzeczywistości nie jest objęty standardem C, który nie pozwala puste zmienne argumenty, tzn. wtedy polegamy na rozszerzeniach kompilatora!

Na początku, puste wywołanie makra (foo()) nadal generuje token, ale pusty. Tak więc makro zliczające zwraca 1 zamiast 0 nawet przy pustym wywołaniu makra. Możemy" łatwo " wyeliminować ten problem, jeśli umieścimy przecinek po __VA_ARGS__ warunkowo , w zależności od tego, czy lista jest pusta, czy nie:

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

To wyglądało łatwo, ale COMMA makro jest dość ciężkie; na szczęście temat jest już poruszany na blogu Jensa Gustedta (dzięki, Jens). Podstawowa sztuczka polega na tym, że makra funkcji nie są rozszerzane, jeśli nie są poprzedzone nawiasami, aby uzyskać dalsze wyjaśnienia, zajrzyj na blog Jensa... Musimy tylko trochę zmodyfikować makra do naszych potrzeb (użyję krótszych nazw i mniej argumentów za zwięzłością).

#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 ,
I teraz jest dobrze...

Kompletny kod w jednym bloku:

/*
 * 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 15:13:48

Nie możesz po prostu używać C++ i nie używać wszystkich innych funkcji C++ Oprócz tej?

Jeśli nadal nie ma tylko ścisłego C, to zamiast tego polecam Funkcje Zmienne .

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 09:30:10

Spróbuj zadeklarować te funkcje jako extern "C++" jeśli twój kompilator obsługuje to, 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/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 10:45:51

Mam nadzieję, że poniższy kod pomoże Ci zrozumieć przeciążenie funkcji

#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/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 04:50:31