Skip to content Skip to sidebar Skip to footer

Boost.python And Boost.function

I want to wrap a boost::function class member so that it can be used in the following way: using namespace boost; using namespace boost::python; struct gui_button_t { 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

  1. a lambda that has no capture can be converted to a function pointer (via sorcery)
  2. 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"