Is there a way to do currying in C?

后端 未结 4 1902
情话喂你
情话喂你 2020-11-27 04:34

Say I have a pointer to a function _stack_push(stack* stk, void* el). I want to be able to call curry(_stack_push, my_stack) and get back a functio

4条回答
  •  伪装坚强ぢ
    2020-11-27 04:48

    Here is an approach to doing currying in C. While this sample application is using the C++ iostream output for convenience it is all C style coding.

    The key to this approach is to have a struct which contains an array of unsigned char and this array is used to build up an argument list for a function. The function to be called is specified as one of the arguments that are pushed into the array. The resulting array is then given to a proxy function which actually executes the closure of function and arguments.

    In this example I provide a couple of type specific helper functions to push arguments into the closure as well as a generic pushMem() function to push a struct or other memory region.

    This approach does require allocation of a memory area that is then used for the closure data. It would be best to use the stack for this memory area so that memory management does not become a problem. There is also the issue as to how large to make the closure storage memory area so that there is sufficient room for the necessary arguments but not so large that excess space in memory or on the stack is taken up by unused space.

    I have experimented with the use of a slightly differently defined closure struct which contains an additional field for the currently used size of the array used to store the closure data. This different closure struct is then used with a modified helper functions which removes the need for the user of the helper functions to maintain their own unsigned char * pointer when adding arguments to the closure struct.

    Notes and caveats

    The following example program was compiled and tested with Visual Studio 2013. The output from this sample is provided below. I am not sure about the use of GCC or CLANG with this example nor am I sure as to issues that may be seen with a 64 bit compiler as I am under the impression that my testing was with a 32 bit application. Also this would approach would only seem to work with functions that use the standard C declaration in which the calling function handles popping the arguments from the stack after the callee returns (__cdecl and not __stdcall in Windows API).

    Since we are building the argument list at run time and then calling a proxy function, this approach does not allow the compiler to perform a check on arguments. This could lead to mysterious failures due to mismatched parameter types which the compiler is unable to flag.

    Example application

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

    Test output

    Output from this sample program. The number in parenthesis is the line number in the main where the function call is made.

     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)
    

提交回复
热议问题