Wrapping templated objects elegantly in Cython

2019-05-19 01:50发布

问题:

This is similar to this question, but it never got any solutions, and I have at least a workaround for the problem, as inelegant as it is.

I am trying to wrap a templated class, Point<_T,__Scale>, where _T=int,float... and __Scale is an int. Now the compiler will generate a separate class for each template value used, but the classes are not related in any way. However, the classes share all their methods, mostly operator overloads for !=<>*&/|, and getters.

In Cython, the only way I have been able to wrap Point<_T,__Scale> is to provide a cdef class for each variation. It works, but results in a lot of copy-pasted code. I would like to know if there is way to share the code between these template class wrappers. Note that I am following the cython method for wrapping described in the tutorials, where a wrapper class holds a *thisptr to the c-object it is wrapping.

// c++ header
template<_T,__Scale>
class Point
{
    Point(_T _x, _T _y) : x(_x), y(_y) {};
    // copy constructor
    template<typename _NT> Point(const Point<_NT, __Scale> &pt) : x( (_T)pt.x ), y( (_T)pt.y ) {};
    _t x, y;
    bool operator == (const Point<_T,__Scale> &pos) const
    bool operator != (const Point<_T,__Scale> &pos) const
    // and many more operators
}

typedef Point<int,1> PointA
typedef Point<int,8> PointB
... //additional typedefs

# cython interface with c++ (not shown: cdef extern from ...)
cdef cppclass Point[_T,__Scale]:
    Point(_T _x, _T _y)
    Point[_NT] Point(const Point[_NT,0] &pt)
    _T x
    _T y
    bint operator == (const Point[_T,__Scale] &pos) const
    bint operator != (const Point[_T,__Scale] &pos) const

# cython wrapper to interface with python (this is where it gets messy)
cdef class pyPointA:
    cdef PointA* thisptr
    def __cinit__(self, int x, int y):
        self.thisptr = new PointA(x,y)
    # everything in this class below this line is copied
    def x(self, setX = None):
        if(setX is None):
            return self.thisptr.x
        else:
            self.thisptr.x = setX

    def y(self, setY = None):
        if(setY is None):
            return self.thisptr.y
        else:
            self.thisptr.y = setY
    # and many more operators

cdef class pyPointB
    cdef PointB* thisptr
    def __cinit__(self, int x, int y):
        self.thisptr = new PointB(x,y)
    # everything in this class below this line is copied
    def x(self, setX = None):
        if(setX is None):
            return self.thisptr.x
        else:
            self.thisptr.x = setX

    def y(self, setY = None):
        if(setY is None):
            return self.thisptr.y
        else:
            self.thisptr.y = setY
    # and many more operators

...
#continue for additional point types

Here is what I have tried:

  • Abstract cython base class to inherit the others from. Ok, but each template type needs different pointers.
  • Store *thisptr as *void. How to deal with the casting?
  • encapsulate thisptr by retrieving it using a getter getPtr(), but still am forced into a single return type. Cannot declare as Python function, because c pointers cannot be wrapped in python objects.
  • write several method that return the correct pointer type, then the getPtr() method returns the right getter for each method to call, to get the pointer from. Unfortunately, only c-functions can return pointers, and they cannot be returned: compiler complains 'not found'.
  • Same as above, except the getPtr() method returns the string name of the getter function, which then we can use getattr() to recall. But cdef method is unable to be found in Python using getattr().

回答1:

The difficulty with this is that the templates are required to be instantiated at C compile time...and of course in C++ there's no common "superclass" for the various template instantiations.

Typically what I would suggest in this situation is using a template engine such as jinja2 to generate all permutations of classes you're interested in.