Things are getting complicated in my world of trying to mesh Python code with my C++.
Essentially, I want to be able to assign a callback function to be used after an HTTP call receives a response, and I want to be able to do this from either C++ or Python.
In other words, I want to be able to call this from C++:
http.get_asyc("www.google.ca", [&](int a) { std::cout << "response recieved: " << a << std::endl; });
and this from Python:
def f(r):
print str.format('response recieved: {}', r)
http.get_async('www.google.ca', f)
I have set up a demo on Coliru that shows exactly what I'm trying to accomplish. Here is the code and the error that I am getting:
C++
#include <boost/python.hpp>
#include <boost/function.hpp>
struct http_manager
{
void get_async(std::string url, boost::function<void(int)> on_response)
{
if (on_response)
{
on_response(42);
}
}
} http;
BOOST_PYTHON_MODULE(example)
{
boost::python::class_<http_manager>("HttpManager", boost::python::no_init)
.def("get_async", &http_manager::get_async);
boost::python::scope().attr("http") = boost::ref(http);
}
Python
import example
def f(r):
print r
example.http.get_async('www.google.ca', f)
Error
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Boost.Python.ArgumentError: Python argument types in
HttpManager.get_async(HttpManager, str, function)
did not match C++ signature:
get_async(http_manager {lvalue}, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, boost::function<void (int)>)
I'm not sure why the function
is not being converted to a boost::function
automatically.
I have asked a vaguely similar question on SO before and got an amazing answer. I also wonder if a similar method in the answer given there could be applied to this use-case as well.
Thank you very much for any and all support!
One solution is to add an overload function:
Then expose just this specific overload:
Or if your don't want to pollute your main class with python stuff then you could create a wrapper class. Things look much cleaner then too:
Update: Another option is to use a python callable to boost function converter. This will address the singleton problem and won't require changes to the main class.
When a function that has been exposed through Boost.Python is invoked, Boost.Python will query its registry to locate a suitable from-Python converter for each of the caller's arguments based on the desired C++ type. If a converter is found that knows how to convert from the Python object to the C++ object, then it will use the converter to construct the C++ object. If no suitable converters are found, then Boost.Python will raise an
ArgumentError
exception.The from-Python converters are registered:
int
andstd::string
boost::python::class<T>
. By default, the resulting Python class will hold an embedded instance of aT
C++ object, and register to-Python and from-Python converters for the Python class and typeT
, using the embedded instance.boost::python::converter::registry::push_back()
The steps of testing for convertibility and constructing an object occur in two distinct steps. As no from-Python converter has been registered for
boost::function<void(int)>
, Boost.Python will raise anArgumentError
exception. Boost.Python will not attempt construct theboost::function<void(int)>
object, despiteboost::function<void(int)>
being constructible from aboost::python::object
.To resolve this, consider using an shim function to defer the construction of
boost::function<void(int)>
until after theboost::python::object
has passed through the Boost.Python layer:Here is a complete example demonstrating this approach:
Interactive usage:
An alternative approach is to explicitly register a from-Python converter for
boost::function<void(int)>
. This has the benefit that all functions exposed through Boost.Python can use the converter (e.g. one would not need to write a shim for each function). However, a convert would need to be registered for each C++ type. Here is an example demonstrating explicitly registering a custom converter forboost::function<void(int)>
andboost::function<void(std::string)>
: