Cython: How to make an array of functions with different signatures

In Cython I would like to store an array of functions, but the functions don't all have the same signature. Specifically, some have two parameters and some three.

I defined the following:

ctypedef long (*func2param)(long param1, long param2)
ctypedef long (*funct3param)(long param1, long param2, long param3)

However, even if I had just one such signature, I'm still not sure how to go about making it work. Trying to assign a cdef function to an array of one of the above types gives me:

func_array2[i] = func_list[i][FUNCTION]

Cannot convert Python object to 'func2param'

Trying to cast gives:

func_array2[i] = <func2param>func_list[i][FUNCTION]

Python objects cannot be cast to pointers of primitive types

1 answer

  • answered 2019-08-13 07:25 DavidW

    I can't think of anything useful you can do with an array of function pointers of unknown type - it isn't safe to call them (since you don't know the signature) and there really isn't anything else to do with function pointers. Therefore you at least need to find some way of storing what type of pointer it is.

    One option would be to store a struct containing both pointers:

    cdef struct FuncPtrStruct:
        func2param f2p
        func3param f3p
    

    You then set one to NULL, store in the other one, and only call the non-NULL one The array specification would then be similar to cdef FuncPtrStruct array[10].

    However, I'd probably use a C Union instead to store both pointers in the same memory location (+ an enum to identify the type). This is a little more effort to set up (you need an enum to define the type of the union, the union itself, and a struct containing the enum and the union); however the advantage is that you can add a lot more different types of function pointers without using more memory (for the "two-type" case the memory is probably equal):

    # cpdef if you want to use the enum from Python too
    cdef enum FuncType:
        twoArg, threeArg
    
    cdef union FuncPtrUnion:
        func2param f2p
        func3param f3p
    
    cdef struct FuncPtrHolder:
        FuncType type_
        FuncPtrUnion value
    

    Just to illustrate how you'd use it:

    cdef long f(long x1, long x2):
        print("f",x1,x2)
        return 0
    
    cdef long g(long x1, long x2, long x3):
        print("g",x1,x2,x3)
        return 1
    
    def example():
        cdef FuncPtrHolder fArray[10]
        for i in range(10):
            if i%2:
                fArray[i].type_ = twoArg
                fArray[i].value.f2p = &f
            else:
                fArray[i].type_ = threeArg
                fArray[i].value.f3p = &g
        # use
        for i in range(10):
            if fArray[i].type_ == twoArg:
                fArray[i].value.f2p(i,i+1)
            elif fArray[i].type_ == threeArg:
                fArray[i].value.f3p(i,i+1,i+2)
    

    It looks like in your code you have some list of Python objects and this is why you're getting compile errors. Without code it's impossible to know why, but I think Cython can automatically generate Python wrappers for cdef functions so I'd guess you've somehow made a list of those wrappers. If you want to handle Python lists of FuncPtrHolder (or otherwise use it from Python) you'd have to wrap it in a cdef class.