Frage

Sagen Sie, ich habe einen Zeiger auf eine Funktion _stack_push(stack* stk, void* el). Ich möchte anrufen können curry(_stack_push, my_stack) und holen Sie sich eine Funktion zurück, die nur nimmt void* el. Ich konnte mir keine Möglichkeit vorstellen, es zu tun, da C keine Laufzeitfunktionsdefinition zulässt, aber ich weiß, dass es hier weitaus klügere Leute gibt als ich hier :). Irgendwelche Ideen?

War es hilfreich?

Lösung

Ich habe ein Papier von Laurent Dami gefunden, in dem das Currying in C/C ++/Objective-C diskutiert wird:

Weitere funktionale Wiederverwendbarkeit in C/C ++/Objective-C mit Curry-Funktionen

Von Interesse daran, wie es in C umgesetzt wird:

Unsere aktuelle Implementierung verwendet vorhandene C -Konstrukte, um den Currying -Mechanismus hinzuzufügen. Dies war viel einfacher zu tun, als den Compiler zu modifizieren, und reicht aus, um das Interesse der Currying zu beweisen. Dieser Ansatz hat jedoch zwei Nachteile. Erstens können Curry-Funktionen nicht überprüft werden und erfordern daher sorgfältige Verwendung, um Fehler zu vermeiden. Zweitens kann die Curry -Funktion die Größe ihrer Argumente nicht kennen und zählt sie so, als wären sie alle die Größe einer Ganzzahl.

Das Papier enthält keine Implementierung von curry(), aber Sie können sich vorstellen, wie es mithilfe der Implementierung implementiert wird Funktionszeiger und Variadische Funktionen.

Andere Tipps

GCC bietet eine Erweiterung für die Definition verschachtelter Funktionen. Obwohl dies kein ISO -Standard C ist, kann dies von Interesse sein, da es die Frage bequem beantworten kann. Kurz gesagt, eine verschachtelte Funktion kann auf die übergeordnete Funktion lokale Variablen zugreifen, und Zeiger können auch von der übergeordneten Funktion zurückgegeben werden.

Hier ist ein kurzes, selbstäugigendes Beispiel:

#include <stdio.h>

typedef int (*two_var_func) (int, int);
typedef int (*one_var_func) (int);

int add_int (int a, int b) {
    return a+b;
}

one_var_func partial (two_var_func f, int a) {
    int g (int b) {
        return f (a, b);
    }
    return g;
}

int main (void) {
    int a = 1;
    int b = 2;
    printf ("%d\n", add_int (a, b));
    printf ("%d\n", partial (add_int, a) (b));
}

Es gibt jedoch eine Einschränkung dieser Konstruktion. Wenn Sie einen Zeiger auf die resultierende Funktion halten, wie in

one_var_func u = partial (add_int, a);

der Funktionsaufruf u(0) kann zu einem unerwarteten Verhalten als Variable führen a die u Lesevorgänge wurden kurz danach zerstört partial beendet.

Sehen Dieser Abschnitt der Dokumentation von GCC.

Hier ist meine erste Vermutung von meinem Kopf (ist möglicherweise nicht die beste Lösung).

Die Curry-Funktion könnte etwas Speicher vom Heap zuweisen und die Parameterwerte in diesen heap-zuallozierten Speicher einfügen. Der Trick besteht dann darin, dass die zurückgegebene Funktion weiß, dass sie ihre Parameter aus diesem haufenallozierten Speicher lesen soll. Wenn es nur eine Instanz der zurückgegebenen Funktion gibt, kann ein Zeiger auf diese Parameter in einem Singleton/Global gespeichert werden. Andernfalls denke ich, dass Curry mehr als eine Instanz der zurückgegebenen Funktion gibt, um jede Instanz der zurückgegebenen Funktion zu erstellen im haufenallozierten Speicher (Durch das Schreiben von Opcodes wie "Rufen Sie diesen Zeiger auf die Parameter", "drücken Sie die Parameter" und "rufen Sie diese andere Funktion in den heap-zuallozierten Speicher auf). In diesem Fall müssen Sie sich vorstellen, ob zugewiesener Speicher ausführbar ist, und vielleicht (ich weiß nicht) sogar Angst vor Antivirenprogrammen haben.

Hier ist ein Ansatz zum Currying in C. Während diese Beispielanwendung die C ++ - iOstream -Ausgabe verwendet, die für die Bequemlichkeit ist, handelt es sich um alles Codierung von C -Stil.

Der Schlüssel zu diesem Ansatz liegt darin, eine zu haben struct die eine Reihe von einer Reihe von enthält unsigned char Und dieses Array wird verwendet, um eine Argumentliste für eine Funktion zu erstellen. Die zu bezeichnete Funktion ist als eines der Argumente angegeben, die in das Array gedrückt werden. Das resultierende Array wird dann einer Proxy -Funktion gegeben, die tatsächlich den Abschluss von Funktion und Argumenten ausführt.

In diesem Beispiel stelle ich ein paar typische spezifische Helferfunktionen zur pushMem() Funktion zum Drücken a struct oder eine andere Speicherregion.

Dieser Ansatz erfordert die Zuordnung eines Speicherbereichs, der dann für die Verschlussdaten verwendet wird. Es ist am besten, den Stapel für diesen Speicherbereich zu verwenden, damit die Speicherverwaltung kein Problem wird. Es gibt auch das Problem, wie groß der Abschluss des Abschlussspeicherspeichers so groß ist, dass genügend Platz für die erforderlichen Argumente ist, aber nicht so groß, dass überschüssiger Speicherplatz oder auf dem Stapel von nicht verwendetem Raum aufgenommen wird.

Ich habe mit der Verwendung einer etwas unterschiedlich definierten Verschlussstruktur experimentiert, die ein zusätzliches Feld für die derzeit verwendete Größe des Arrays enthält, das zum Speichern der Verschlussdaten verwendet wird. Diese unterschiedliche Verschlussstruktur wird dann mit modifizierten Helferfunktionen verwendet, wodurch der Benutzer der Helferfunktionen erforderlich ist, um ihre eigenen zu pflegen unsigned char * Zeiger beim Hinzufügen von Argumenten zur Schließstruktur.

Notizen und Vorbehalte

Das folgende Beispielprogramm wurde mit Visual Studio 2013 zusammengestellt und getestet. Die Ausgabe aus dieser Stichprobe ist unten bereitgestellt. Ich bin mir nicht sicher, ob GCC oder Clang mit diesem Beispiel verwendet werden kann, und ich bin mir sicher, dass Probleme mit einem 64 -Bit -Compiler zu sehen sind, da ich unter dem Eindruck bin, dass mein Test mit einer 32 -Bit -Anwendung war. Auch dieser Ansatz würde nur mit Funktionen funktionieren, die die Standard -C -Deklaration verwenden, bei der die Anruffunktion die Argumente aus dem Stapel nach der Rückkehr der Callee übernimmt (__cdecl und nicht __stdcall in Windows API).

Da wir die Argumentliste zur Laufzeit erstellen und dann eine Proxy -Funktion aufrufen, erlaubt dieser Ansatz dem Compiler nicht, eine Überprüfung der Argumente durchzuführen. Dies könnte zu mysteriösen Fehlern aufgrund nicht übereinstimmender Parametertypen führen, die der Compiler nicht kennzeichnen kann.

Beispielanwendung

// currytest.cpp : Defines the entry point for the console application.
//
// while this is C++ usng the standard C++ I/O it is written in
// a C style so as to demonstrate use of currying with C.
//
// this example shows implementing a closure with C function pointers
// along with arguments of various kinds. the closure is then used
// to provide a saved state which is used with other functions.

#include "stdafx.h"
#include <iostream>

// notation is used in the following defines
//   - tname is used to represent type name for a type
//   - cname is used to represent the closure type name that was defined
//   - fname is used to represent the function name

#define CLOSURE_MEM(tname,size) \
    typedef struct { \
        union { \
            void *p; \
            unsigned char args[size + sizeof(void *)]; \
        }; \
    } tname;

#define CLOSURE_ARGS(x,cname) *(cname *)(((x).args) + sizeof(void *))
#define CLOSURE_FTYPE(tname,m) ((tname((*)(...)))(m).p)

// define a call function that calls specified function, fname,
// that returns a value of type tname using the specified closure
// type of cname.
#define CLOSURE_FUNC(fname, tname, cname) \
    tname fname (cname m) \
    { \
        return ((tname((*)(...)))m.p)(CLOSURE_ARGS(m,cname)); \
    }

// helper functions that are used to build the closure.
unsigned char * pushPtr(unsigned char *pDest, void *ptr) {
    *(void * *)pDest = ptr;
    return pDest + sizeof(void *);
}

unsigned char * pushInt(unsigned char *pDest, int i) {
    *(int *)pDest = i;
    return pDest + sizeof(int);
}

unsigned char * pushFloat(unsigned char *pDest, float f) {
    *(float *)pDest = f;
    return pDest + sizeof(float);
}

unsigned char * pushMem(unsigned char *pDest, void *p, size_t nBytes) {
    memcpy(pDest, p, nBytes);
    return pDest + nBytes;
}


// test functions that show they are called and have arguments.
int func1(int i, int j) {
    std::cout << " func1 " << i << " " << j;
    return i + 2;
}

int func2(int i) {
    std::cout << " func2 " << i;
    return i + 3;
}

float func3(float f) {
    std::cout << " func3 " << f;
    return f + 2.0;
}

float func4(float f) {
    std::cout << " func4 " << f;
    return f + 3.0;
}

typedef struct {
    int i;
    char *xc;
} XStruct;

int func21(XStruct m) {
    std::cout << " fun21 " << m.i << " " << m.xc << ";";
    return m.i + 10;
}

int func22(XStruct *m) {
    std::cout << " fun22 " << m->i << " " << m->xc << ";";
    return m->i + 10;
}

void func33(int i, int j) {
    std::cout << " func33 " << i << " " << j;
}

// define my closure memory type along with the function(s) using it.

CLOSURE_MEM(XClosure2, 256)           // closure memory
CLOSURE_FUNC(doit, int, XClosure2)    // closure execution for return int
CLOSURE_FUNC(doitf, float, XClosure2) // closure execution for return float
CLOSURE_FUNC(doitv, void, XClosure2)  // closure execution for void

// a function that accepts a closure, adds additional arguments and
// then calls the function that is saved as part of the closure.
int doitargs(XClosure2 *m, unsigned char *x, int a1, int a2) {
    x = pushInt(x, a1);
    x = pushInt(x, a2);
    return CLOSURE_FTYPE(int, *m)(CLOSURE_ARGS(*m, XClosure2));
}

int _tmain(int argc, _TCHAR* argv[])
{
    int k = func2(func1(3, 23));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    XClosure2 myClosure;
    unsigned char *x;

    x = myClosure.args;
    x = pushPtr(x, func1);
    x = pushInt(x, 4);
    x = pushInt(x, 20);
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func1);
    x = pushInt(x, 4);
    pushInt(x, 24);               // call with second arg 24
    k = func2(doit(myClosure));   // first call with closure
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;
    pushInt(x, 14);              // call with second arg now 14 not 24
    k = func2(doit(myClosure));  // second call with closure, different value
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    k = func2(doitargs(&myClosure, x, 16, 0));  // second call with closure, different value
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    // further explorations of other argument types

    XStruct xs;

    xs.i = 8;
    xs.xc = "take 1";
    x = myClosure.args;
    x = pushPtr(x, func21);
    x = pushMem(x, &xs, sizeof(xs));
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    xs.i = 11;
    xs.xc = "take 2";
    x = myClosure.args;
    x = pushPtr(x, func22);
    x = pushPtr(x, &xs);
    k = func2(doit(myClosure));
    std::cout << " main (" << __LINE__ << ") " << k << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func3);
    x = pushFloat(x, 4.0);

    float dof = func4(doitf(myClosure));
    std::cout << " main (" << __LINE__ << ") " << dof << std::endl;

    x = myClosure.args;
    x = pushPtr(x, func33);
    x = pushInt(x, 6);
    x = pushInt(x, 26);
    doitv(myClosure);
    std::cout << " main (" << __LINE__ << ") " << std::endl;

    return 0;
}

Testausgabe

Ausgabe aus diesem Beispielprogramm. Die Anzahl in Klammern ist die Zeilennummer in der Hauptnummer, bei der der Funktionsaufruf erfolgt.

 func1 3 23 func2 5 main (118) 8
 func1 4 20 func2 6 main (128) 9
 func1 4 24 func2 6 main (135) 9
 func1 4 14 func2 6 main (138) 9
 func1 4 16 func2 6 main (141) 9
 fun21 8 take 1; func2 18 main (153) 21
 fun22 11 take 2; func2 21 main (161) 24
 func3 4 func4 6 main (168) 9
 func33 6 26 main (175)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top