4

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<void()> on_pressed;
};

class_<gui_button_t>("GuiButton", init<>())
    .def("on_pressed", &gui_button_t::on_pressed);

And then in Python:

def callback_function():
    print 'button has been pressed'

button = GuiButton()
button.on_pressed = callback_function
button.on_pressed() # function should be callable from C++ or Python

However, trying this yields an enormous amount of errors regarding class template parameters and so on.

I've done a bit of searching but haven't been able to find an answer I've been looking for. The following article sort of come close but they don't touch directly on the subject.

http://bfroehle.com/2011/07/18/boost-python-and-boost-function-ii/

What am I doing wrong here? What do I need to do to get the desired interface for this functionality?

Many thanks in advance.

Colin Basnett
  • 4,052
  • 2
  • 30
  • 49

1 Answers1

5

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...> struct typelist { };

template <typename C, typename R, typename... Args>                        \
struct closure_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>
class wrap { ... };

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>
class wrap {
public:
    static auto make_pointer() {
        return make_pointer_impl(typename closure_traits<F>::args{});
    }

private:
    template <typename... Args>
    static auto make_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 it
        return +[](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):

#define WRAP_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:

static auto set_callable() {
    return make_setter_impl(
        typelist<typename closure_traits<F>::result_type>{},
        typename closure_traits<F>::args{});
}

template <typename R, typename... Args>
static auto make_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 void
template <typename... Args>
static auto make_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:

static void 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>
struct concat;

template <typename T1, typename T2>
using concat_t = typename concat<T1, T2>::type;

template <typename... A1, typename... A2>
struct concat<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>
struct make_fn_ptr;

template <typename R, typename... Args>
struct make_fn_ptr<R, typelist<Args...>> {
    using type = R(*)(Args...);
};

template <typename R, typename T>
using make_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.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Wow, quick response! I have to jet off to work now but I will review this later today. :) – Colin Basnett Jul 23 '15 at 15:13
  • I'm compiling against Visual Studio 2013 which has a bit more stilted implementation of return-type deduction using `auto`. I get two errors when trying to compile the posted code: `error C3551: expected a trailing return type` on `static auto make_pointer()` and `static auto make_pointer_impl(typelist)`. – Colin Basnett Jul 24 '15 at 06:50
  • @cmbasnett Added the code necessary to make this work on C++11. – Barry Jul 24 '15 at 10:44
  • Thank you! It appears to work (it compiles at least), but I only had about 5 minutes this morning to try it out, still need to see if the C++ can trigger an assigned Python function. Will definitely try it out when I get home! After that I'll try and dissect all the arcane templating that's going on :P – Colin Basnett Jul 24 '15 at 16:09
  • Okay, so I tried it out but I think a key element to the puzzle is missing. Namely, the ability to assign *Python* functions to the wrapped function object, so that when the C++ triggers the `on_pressed` event, it runs the Python function I defined by calling this: `button.on_pressed = callback_function` where `callback_function` is defined as `def callback_function(button): print 'yo'` – Colin Basnett Jul 25 '15 at 02:07
  • @cmbasnett I don't understand the issue. If you assigned a callable to `on_pressed`, you can call it. Are you asking how to assign a Python function to a C++ functor? – Barry Jul 25 '15 at 08:53
  • The general setup is that I am going to end up having the C++ calling the `on_pressed` functor, and I merely want to define and assign what gets run in Python, I will likely not be calling `on_pressed` from Python. – Colin Basnett Jul 27 '15 at 00:21
  • @cmbasnett Added a section on assignment. – Barry Jul 27 '15 at 13:59
  • Regarding the setter functions (ie. `set_callable`), how should I define the return type? (auto return type doesn't work in C++11 ;)) – Colin Basnett Jul 27 '15 at 15:05
  • @cmbasnett Exact way as I showed for the other one. – Barry Jul 27 '15 at 15:07
  • I tried setting the return type as `R` but to no avail, yielded a compile error; pretend I'm not a master of template meta-programming. ;) I'm gonna assign you the bounty already though, since you've been very patient and helpful. – Colin Basnett Jul 28 '15 at 18:46
  • @cmbasnett The way I wrote it in the demo? Could always ask a new question, big metaprogramming crowd on SO :) – Barry Jul 28 '15 at 22:37