Search code examples
c++c++11boostboost-pythonstd-function

Passing a function as function argument in Boost.Python


I have a C++ function ExecuteFunction that takes as input another function f.

I would like to expose ExecuteFunction in Python with Boost.Python, and call it from Python with either Python functions or other C++ functions as arguments. I've found plenty of information on how to pass Python functions to ExecuteFunction [1][2][3], but can't figure out how to specify a plain C++ function as argument.

Here is what I'm currently doing:

#include <boost/python.hpp>

typedef std::function<int(int)> Function;

int ExecuteFunction(int x, Function f) {
  return f(x);
};

int Half(int x) {
  return x / 2;
};

struct Square {
  int operator()(int x) const {
    return x * x;
  };
};

BOOST_PYTHON_MODULE(Test) {
  boost::python::def("ExecuteFunction", ExecuteFunction);
  boost::python::def("Half", Half);
  boost::python::class_<Square>("Square")
    .def("__call__", &Square::operator());
};

On the Python side I would like to do the following:

import Test

def Double(x):
    return x * 2

a = Test.ExecuteFunction(2, Test.Half)
b = Test.ExecuteFunction(2, Test.Square())
c = Test.ExecuteFunction(2, Double)

But the calls to Test.ExecuteFunction fail with the following tracebacks respectively:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Test.ExecuteFunction(int, Boost.Python.function)
did not match C++ signature:
    ExecuteFunction(int, std::function<int (int)>)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Test.ExecuteFunction(int, Square)
did not match C++ signature:
    ExecuteFunction(int, std::function<int (int)>)
Boost.Python.ArgumentError: Python argument types in
    Test.ExecuteFunction(int, function)
did not match C++ signature:
    ExecuteFunction(int, std::function<int (int)>)

I'm not really interested in calling Half and Square on the Python side (although being able to do so would be nice). I would be satisfied with being able to specify the right argument to ExecuteFunction. How could I do this?


Solution

  • As far as I can tell exposed C++ functions are always converted to a Python equivalent when constructed/invoked in Python. Hence ExecuteFunction should take as input a boost::python::object, not a std::function, even if you only intend to pass C++ functions as argument. You can then call the function and retrieve its return value with boost::python::call.

    The following wound up working:

    #include <boost/python.hpp>
    
    int ExecuteFunctionWrapper(int x, const boost::python::object& f) {
      return boost::python::call<int>(f.ptr(), x);
    };
    
    int Half(int x) {
      return x / 2;
    };
    
    struct Square {
      int operator()(int x) const {
        return x * x;
      };
    };
    
    BOOST_PYTHON_MODULE(Test) {
      boost::python::def("ExecuteFunction", ExecuteFunctionWrapper);
      boost::python::def("Half", Half);
      boost::python::class_<Square>("Square")
        .def("__call__", &Square::operator());
    };
    
    import Test
    
    def Double(x):
        return x * 2
    
    a = Test.ExecuteFunction(4, Test.Half)
    b = Test.ExecuteFunction(4, Test.Square())
    c = Test.ExecuteFunction(4, Double)
    print(a, b, c)