Photo by Anastasia Zhenina / Unsplash

A Method to Convert a Python Callable to a C++ std::function Variable

SWIG Mar 15, 2025

For now, SWIG does not support std::function yet. So, directly calling to the Python bindings of C++ methods whose parameter is a std::function variable is not possible. But there is a method to convert a Python callable to a C++ std::function variable without any modification to the original C++ source code.
For example, the following class has a method which takes a std::function<bool(int)> variable as parameter:

#include <functional>
#include <iostream>

class MyClass {
public:
    void setCallback(std::function<bool(int)> callback) {
        m_callback = callback;
    }

    void executeCallback(int value) {
        if (m_callback) {
            bool result = m_callback(value);
            std::cout << "Callback result: " << result << std::endl;
        }
    }

private:
    std::function<bool(int)> m_callback;
};

To make the setCallback method above usable in Python, the first step is to define a wrapper for a Python callable in the SWIG interface like this:

%{
#include <functional>
#include <Python.h>

std::function<bool(int)> make_std_function(PyObject* callable) {
    return [callable](int value) -> bool {
        PyGILState_STATE state  = PyGILState_Ensure();
        PyObject*        arg    = PyLong_FromLong(value);
        PyObject*        result = PyObject_CallFunctionObjArgs(callable, arg, NULL);
        Py_DECREF(arg);
        
        if (!result) {
            PyErr_Print();
            PyGILState_Release(state);
            return false;
        }
        bool ret = PyObject_IsTrue(result);
        Py_DECREF(result);
        PyGILState_Release(state);
        
        return ret;
    };
}
%}

As shown above, the wrapper is able to convert a Python callable to a std::function<bool(int)> variable. And here is how to use it.

%extend MyClass {
    void setCallback(PyObject* callable) {
        if (!PyCallable_Check(callable)) {
            PyErr_SetString(PyExc_TypeError, "Argument must be a callable");
            return;
        }
        $self->setCallback(make_std_function(callable));
    }
}

At last, we can directly use setCallback in Python now. And Python lambda is also supported.

import example

def my_callback(value):
    print(f"Callback called with value: {value}")
    return value > 0

obj = example.MyClass()
obj.setCallback(my_callback)
obj.executeCallback(10)
obj.executeCallback(-5)

positive = lambda value: value > 0
obj.setCallback(positive)
obj.executeCallback(10)
obj.executeCallback(-5)

However, this is just a simple example and still needs more work in memory management.

Tags