Boost.python And Boost.function
Solution 1:
Boost.Python only accepts pointers to functions and pointers to member functions. So what we need to do is convert our callable into a function pointer. The key ideas here are that
- a lambda that has no capture can be converted to a function pointer (via sorcery)
- function pointers are interpreted the same way as member functions in Python: the first argument is
self
So in your case, what we need to do is generate this lambda:
+[](gui_button_t* self) {
self->on_pressed();
}
You can already use that as-is with Boost.Python, since that is a perfectly normal pointer to function. However, we want a solution that will work for any callable member. Why just support boost::function
when you can support anything?
We'll start with @Columbo's closure_traits
, but additionally adding a way to pull out the argument list;
template <typename...> structtypelist { };
template <typename C, typename R, typename... Args> \
structclosure_traits<R (C::*) (Args... REM_CTOR var) cv> \
{ \
using arity = std::integral_constant<std::size_t, sizeof...(Args) >; \
using is_variadic = std::integral_constant<bool, is_var>; \
using is_const = std::is_const<int cv>; \
\
using result_type = R; \
\
template <std::size_t i> \
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
\
using args = typelist<Args...>; \
};
Then we'll write a wrapper for any callable member. Since our lambda can take NO capture, we have to take the callable as a template parameter:
template <typename CLS, typename F, F CLS::*callable>
classwrap { ... };
I will use C++14's auto
return type deduction to save some typing. We make a top-level make_pointer()
static member function that just forwards to a helper member function that additionally takes the arguments. The full wrap
looks like:
template <typename CLS, typename F, F CLS::*callable>
classwrap {
public:
staticautomake_pointer(){
returnmake_pointer_impl(typename closure_traits<F>::args{});
}
private:
template <typename... Args>
staticautomake_pointer_impl(typelist<Args...> ){
// here is our lambda that takes the CLS as the first argument// and then the rest of the callable's arguments,// and just calls itreturn +[](CLS* self, Args... args) {
return (self->*callable)(args...);
};
}
};
Which we can use to wrap your button:
void (*f)(gui_button_t*) = wrap<gui_button_t,
decltype(gui_button_t::on_pressed),
&gui_button_t::on_pressed
>::make_pointer();
That's a little verbose and repetitive, so let's just make a macro (sigh):
#defineWRAP_MEM(CLS, MEM) wrap<CLS, decltype(CLS::MEM), &CLS::MEM>::make_pointer()
So we get:
void (*f)(gui_button_t*) = WRAP_MEM(gui_button_t, on_pressed);
f(some_button); // calls some_button->on_pressed()
Since this gives us a pointer to function, we can use this directly with the normal Boost.Python API:
class_<gui_button_t>("GuiButton", init<>())
.def("on_pressed", WRAP_MEM(gui_button_t, on_pressed));
Demo demonstrating function pointers to a member std::function
and a member struct
with an operator()
.
The above gets you the ability to expose a callable. If you want to additionally be able to do assignment, i.e.:
button = GuiButton()
button.on_pressed = callback_function
button.on_pressed()
We'll need to do something else. You can't expose operator=
in a meaningful way in Python, so to support the above functionality, you'd have to override __setattr__
instead. Now, if you were open to:
button.set_on_pressed(callback_function)
we could extend the above wrap
solution to add a setter, whose implementation would be, in the vein of the above:
staticautoset_callable(){
returnmake_setter_impl(
typelist<typename closure_traits<F>::result_type>{},
typename closure_traits<F>::args{});
}
template <typename R, typename... Args>
staticautomake_setter_impl(typelist<R>, typelist<Args...> ){
return +[](CLS* self, py::object cb) {
(self->*callable) = [cb](Args... args) {
return py::extract<R>(
cb(args...))();
};
};
}
// need a separate overload just for voidtemplate <typename... Args>
staticautomake_setter_impl(typelist<void>, typelist<Args...> ){
return +[](CLS* self, py::object cb) {
(self->*callable) = [cb](Args... args) {
cb(args...);
};
};
}
#define SET_MEM(CLS, MEM) wrap<CLS, decltype(CLS::MEM), &CLS::MEM>::set_callable()
Which you could then expose via:
.def("set_on_pressed", SET_MEM(button, on_pressed))
However, if you insist on supporting direct-assignment, then you would need to additionally expose something like:
staticvoid setattr(py::object obj, std::string attr, py::object val)
{
if (attr == "on_pressed") {
button& b = py::extract<button&>(obj);
SET_MEM(button, on_pressed)(&b, val);
}
else {
py::str attr_str(attr);
if (PyObject_GenericSetAttr(obj.ptr(), attr_str.ptr(), val.ptr()) {
py::throw_error_already_set();
}
}
}
.def("__setattr__", &button::setattr);
That would work, but you'd have to add more cases for each functor you want to set. If you only have one functor-like object per class, probably not a big deal, and can even write a higher order function to product a specific setattr
-like function for a given attribute name. But if you have multiples, it's going to steadily get worse than the simple set_on_pressed
solution.
If C++14 is not available, we'll have to just explicitly specify the return type of make_pointer
. We'll need a few handy type traits. concat
:
template <typename T1, typename T2>
structconcat;
template <typename T1, typename T2>
usingconcat_t = typename concat<T1, T2>::type;
template <typename... A1, typename... A2>
structconcat<typelist<A1...>, typelist<A2...>> {
using type = typelist<A1..., A2...>;
};
And then something to turn a return type and a typelist
into a function pointer:
template <typename R, typename T>
structmake_fn_ptr;
template <typename R, typename... Args>
structmake_fn_ptr<R, typelist<Args...>> {
using type = R(*)(Args...);
};
template <typename R, typename T>
usingmake_fn_ptr_t = typename make_fn_ptr<R, T>::type;
And then within wrap
, we can just define a result type as:
using R = make_fn_ptr_t<
typename closure_traits<F>::result_type,
concat_t<
typelist<CLS*>,
typename closure_traits<F>::args
>
>;
and use that instead of auto
. C++11 Demo.
Post a Comment for "Boost.python And Boost.function"