Wie erreicht man eine Funktionsüberladung in C?

Lesezeit: 11 Minuten

Benutzeravatar von FL4SOF
FL4SOF

Gibt es eine Möglichkeit, eine Funktionsüberladung in C zu erreichen? Ich suche nach einfachen Funktionen, die gerne überladen werden

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

Ich denke, es gibt keinen direkten Weg; Ich suche nach Problemumgehungen, falls es welche gibt.

  • Warum würden Sie das tun wollen? C hat keine polymorphen Fähigkeiten. Also ist foo(random type) unmöglich. Machen Sie einfach echte Funktionen foo_i, foo_ch, foo_d usw.

    – Jmucchiello

    26. Januar 2009 um 13:10 Uhr

  • Sie können den bösen Weg gehen, indem Sie void-Zeiger und Typ-IDs verwenden.

    – alk

    20. Oktober 2011 um 9:28 Uhr

  • Ich glaube, ich sollte darauf aufmerksam machen, dass sich die Antwort auf diese Frage geändert hat, seit sie ursprünglich gestellt wurde, mit der neuen C-Norm.

    – Leuschenko

    7. September 2014 um 0:05 Uhr

Benutzeravatar von Leushenko
Leuschenko

Ja!

In der Zeit, seit diese Frage gestellt wurde, hat Standard C (keine Erweiterungen) effektiv funktioniert gewonnen Unterstützung für das Überladen von Funktionen (nicht Operatoren), dank der Hinzufügung von _Generic Schlüsselwort in C11. (unterstützt in GCC seit Version 4.9)

(Das Überladen ist nicht wirklich in der in der Frage gezeigten Weise “eingebaut”, aber es ist kinderleicht, etwas zu implementieren, das so funktioniert.)

_Generic ist ein Kompilierungsoperator in derselben Familie wie sizeof und _Alignof. Es ist im Standardabschnitt 6.5.1.1 beschrieben. Es akzeptiert zwei Hauptparameter: einen Ausdruck (der zur Laufzeit nicht ausgewertet wird) und eine Typ/Ausdruck-Zuordnungsliste, die ein bisschen wie a aussieht switch Block. _Generic Ruft den Gesamttyp des Ausdrucks ab und “schaltet” ihn dann um, um den Endergebnisausdruck in der Liste für seinen Typ auszuwählen:

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

Der obige Ausdruck ergibt sich zu 2 – der Typ des steuernden Ausdrucks ist intalso wählt es den mit verknüpften Ausdruck aus int als Wert. Davon bleibt zur Laufzeit nichts übrig. (Das default -Klausel ist optional: Wenn Sie sie weglassen und der Typ nicht übereinstimmt, führt dies zu einem Kompilierungsfehler.)

Dies ist nützlich für das Überladen von Funktionen, da es vom C-Präprozessor eingefügt und ein Ergebnisausdruck basierend auf dem Typ der Argumente ausgewählt werden kann, die an das steuernde Makro übergeben werden. Also (Beispiel aus dem C-Standard):

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

Dieses Makro implementiert eine überladene cbrt Operation, indem der Typ des Arguments an das Makro gesendet wird, eine geeignete Implementierungsfunktion ausgewählt wird und dann das ursprüngliche Makroargument an diese Funktion übergeben wird.

Um Ihr ursprüngliches Beispiel zu implementieren, könnten wir Folgendes tun:

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 diesem Fall hätten wir a verwenden können default: Assoziation für den dritten Fall, aber das zeigt nicht, wie man das Prinzip auf mehrere Argumente ausdehnt. Das Endergebnis ist, dass Sie verwenden können foo(...) in Ihrem Code, ohne sich Sorgen zu machen (viel[1]) über die Art seiner Argumente.


Für kompliziertere Situationen, z. B. Funktionen, die eine größere Anzahl von Argumenten überladen, oder variierende Zahlen, können Sie Hilfsmakros verwenden, um automatisch statische Dispatch-Strukturen zu generieren:

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

(Implementierung hier) Mit etwas Aufwand können Sie also die Menge der Boilerplate so reduzieren, dass sie ziemlich genau wie eine Sprache mit nativer Unterstützung für das Überladen aussieht.

Nebenbei war es schon möglich auf dem zu überladen Nummer von Argumenten (nicht der Typ) in C99.


[1] Beachten Sie jedoch, dass die Art und Weise, wie C Typen auswertet, Sie stolpern lassen könnte. Dies wird wählen foo_int wenn Sie beispielsweise versuchen, ein Zeichenliteral zu übergeben, und Sie ein wenig herumspielen müssen, wenn Sie möchten, dass Ihre Überladungen Zeichenfolgenliterale unterstützen. Insgesamt aber immer noch ziemlich cool.

  • Basierend auf Ihrem Beispiel sieht es so aus, als ob nur Funktionen wie Makros überladen werden. Lassen Sie mich sehen, ob ich das richtig verstehe: Wenn Sie Funktionen überladen möchten, verwenden Sie einfach den Präprozessor, um den Funktionsaufruf basierend auf den übergebenen Datentypen umzuleiten, oder?

    – Nik

    5. Februar 2015 um 17:47 Uhr


  • Leider gehe ich davon aus, dass MISRA diese Funktion nicht übernehmen wird, wenn C11 beginnt, sich durchzusetzen, und zwar aus den gleichen Gründen, aus denen sie Listen mit variablen Argumenten verbieten. Ich versuche in meiner Welt ziemlich nah an MISRA dran zu bleiben.

    – Nik

    5. Februar 2015 um 17:50 Uhr


  • @ Nick, das ist alles Überladen. Es wird in anderen Sprachen nur implizit gehandhabt (z. B. können Sie in keiner Sprache wirklich “einen Zeiger auf eine überladene Funktion” erhalten, da das Überladen mehrere Körper impliziert). Beachten Sie, dass dies nicht allein vom Präprozessor erledigt werden kann, es erfordert irgendeine Art von Typ-Dispatch; der Präprozessor ändert nur, wie es aussieht.

    – Leuschenko

    5. Februar 2015 um 20:16 Uhr

  • Als jemand, der sich mit C99 ziemlich gut auskennt und lernen möchte, wie das geht, scheint dies zu kompliziert zu sein, selbst für C.

    – Tyler Crompton

    3. Mai 2016 um 20:06 Uhr


  • @TylerCrompton Es wird zur Kompilierzeit ausgewertet.

    – JAB

    21. April 2017 um 14:45 Uhr

Es gibt wenige Möglichkeiten:

  1. Funktionen im printf-Stil (als Argument eingeben)
  2. Funktionen im OpenGL-Stil (geben Sie den Funktionsnamen ein)
  3. c-Teilmenge von c++ (wenn Sie einen c++-Compiler verwenden können)

  • Können Sie erklären oder Links für Funktionen im Opengl-Stil bereitstellen?

    – FL4SOF

    26. Januar 2009 um 12:00 Uhr

  • @Laser: Hier ist eine einfache printf-ähnliche Funktionsimplementierung.

    – Alexey Frunze

    20. Oktober 2011 um 10:34 Uhr

  • Nein. printf ist keine Funktionsüberladung. es verwendet vararg !!! Und C unterstützt kein Funktionsüberladen.

    – Hqt

    29. Juli 2012 um 9:49 Uhr

  • @hqt In der Antwort wird das Wort Überladung nie erwähnt.

    – Remmy

    20. Juni 2013 um 16:12 Uhr

  • @kyrias Wenn es bei der Antwort nicht um Überladung geht, liegt es an der falschen Frage

    – Michael Mrozek

    11. Mai 2018 um 21:54 Uhr

Benutzeravatar von a2800276
a2800276

Wie bereits erwähnt, wird das Überladen in dem Sinne, den Sie meinen, von C nicht unterstützt. Eine gängige Redewendung zur Lösung des Problems besteht darin, die Funktion dazu zu bringen, a zu akzeptieren Gewerkschaft markiert. Realisiert wird dies durch a struct Parameter, wo die struct selbst besteht aus einer Art Typindikator, z. B. an enumund ein union der verschiedenen Arten von Werten. Beispiel:

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

  • Warum machst du nicht einfach alle whatevers in separate Funktionen (set_int, set_float, etc). Dann wird “Taggen mit dem Typ” zu “Typnamen zum Funktionsnamen hinzufügen”. Die Version in dieser Antwort erfordert mehr Eingaben, mehr Laufzeitkosten, eine größere Wahrscheinlichkeit von Fehlern, die zur Kompilierzeit nicht abgefangen werden … Ich sehe es nicht überhaupt einen Vorteil Dinge so zu tun! 16 positive Stimmen?!

    – Ben

    29. Januar 2013 um 21:24 Uhr

  • Ben, diese Antwort wird positiv bewertet, weil sie beantwortet die Frage, statt einfach zu sagen „tu das nicht“. Sie haben Recht, dass es in C idiomatischer ist, separate Funktionen zu verwenden, aber wenn man Polymorphismus in C haben möchte, ist dies ein guter Weg, dies zu tun. Darüber hinaus zeigt diese Antwort, wie Sie Laufzeitpolymorphismus in einem Compiler oder einer VM implementieren würden: Taggen Sie den Wert mit einem Typ und senden Sie dann basierend darauf. Es ist somit eine ausgezeichnete Antwort auf die ursprüngliche Frage.

    – Nils von Barth

    8. November 2014 um 23:12 Uhr


  • @NilsvonBarth Aber die Frage betrifft die Überladung, nicht Polymorphismus, und die Verwendung des letzteren, um den ersteren zu emulieren, ist ein schlechter Rat.

    – Dmitri Grigorjew

    21. Juli um 8:32 Uhr

Benutzeravatar von Jay Taylor
Jay Taylor

Hier ist das klarste und prägnanteste Beispiel, das ich gefunden habe, um das Überladen von Funktionen in C zu demonstrieren:

#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

Benutzeravatar von Spudd86
Spudd86

Wenn Ihr Compiler gcc ist und es Ihnen nichts ausmacht, jedes Mal, wenn Sie eine neue Überladung hinzufügen, manuelle Aktualisierungen vorzunehmen, können Sie Makromagie anwenden und das gewünschte Ergebnis in Bezug auf Aufrufer erzielen. Es ist nicht so schön zu schreiben … aber es ist möglich

Schauen Sie sich __builtin_types_compatible_p an und verwenden Sie es dann, um ein Makro zu definieren, das so etwas tut

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

aber ja böse, tu es einfach nicht

BEARBEITEN: C1X wird Unterstützung für typgenerische Ausdrücke erhalten, die wie folgt aussehen:

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

Benutzeravatar von Christoph
Christoph

Der folgende Ansatz ist ähnlich wie a2800276‘s, aber mit etwas C99-Makromagie hinzugefügt:

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

Benutzeravatar von nbanic
nbanisch

Mehr oder weniger.

Hier gehen Sie zum Beispiel:

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

Es wird 0 und hallo .. von printA und printB ausgeben.

  • int main(int argc, char** argv) { int a=0; Druck(a); print(“Hallo”); Rückkehr (EXIT_SUCCESS); } wird 0 und hallo ausgeben .. von printA und printB …

    – Kapitän Barbossa

    22. Oktober 2012 um 11:24 Uhr

  • __builtin_types_compatible_p, ist das nicht GCC-Compiler-spezifisch?

    – Sogartar

    22. März 2013 um 12:35 Uhr

1426660cookie-checkWie erreicht man eine Funktionsüberladung in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy