Search code examples
c++callbackcython

How to inherit from a base C++ in Cython and pass `self` to inherited method


I have the following library:

namespace worker {
class Worker {
public:
  virtual Worker();
  virtual void doWork() = 0; 
};

class Factory {
  public:
    void addWorker(std::unique_ptr<Worker> worker) {
      workers.push_back(std::move(worker));
    }
    void doWork() {
      for (auto& worker : workers) { worker->doWork(); }
    }
  private:
    std::vector<std::unique_ptr<Worker>> workers; 
};
}

I would like to wrap this in Cython and allow implementing workers in Python, pass them through Cython to C++. However, I don't know how to get self when inheriting:

cdef extern from "Worker.h" namespace "worker":
    cdef cppclass cWorker "worker::Worker":
        void doWork() except +

cdef cppclass WorkerWrapper(cWorker):
    object worker

    void __cinit__(self, object worker):
        self.worker = worker

    void doWork():
        # No self!
        self.worker.doWork()

Also, I don't know how to create workers because when I do:

make_unique[WorkerWrapp](worker_class())

I get:

Python object cannot be passed as a varargs parameter.

Solution

  • The first issue:

    Python object cannot be passed as a varargs parameter.
    

    is because unique_ptr is declared to Cython with a signature of T unique_ptr(...) to avoid Cython having to reason about the arguments. It won't accept reference counted types for ... varargs definitions because it's hard to reason about what might happen to them.

    In this case I think you've got two options:

    1. Cast the object to a PyObject* (you get PyObject by from cpython cimport PyObject) - this is just an opaque pointer type from Python's point of view so it hides the "objectyness" from Cython. make_unique[WorkerWrapp](<PyObject*>worker_class())
    2. Avoid make_unique in favour of unique_ptr[WorkerWrapper](new WorkerWrapper(worker_class()))

    For the second question you just drop the self - inside cppclass it's implicit (like in C++):

        void doWork():
            worker.doWork()
    

    (Personally I think this inconsistency might have been a mistake...)