Boost.Python и Boost.Function

Я хочу завернуть boost::function член класса, чтобы его можно было использовать следующим образом:

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);

А потом в 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

Однако попытка этого приводит к огромному количеству ошибок в отношении параметров шаблона класса и так далее.

Я немного искал, но не смог найти ответ, который искал. Следующая статья как бы подходит, но не затрагивает непосредственно тему.

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

Что я здесь не так делаю? Что мне нужно сделать, чтобы получить желаемый интерфейс для этой функции?

Спасибо заранее.

1 ответ

Решение

Boost.Python принимает только указатели на функции и указатели на функции-члены. Поэтому нам нужно преобразовать наш вызываемый объект в указатель на функцию. Ключевые идеи здесь заключаются в том, что

  1. лямбда, которая не имеет перехвата, может быть преобразована в указатель на функцию (через колдовство)
  2. указатели на функции интерпретируются так же, как и функции-члены в Python: первый аргумент self

Итак, в вашем случае нам нужно сгенерировать эту лямбду:

+[](gui_button_t* self) {
    self->on_pressed();
}

Вы уже можете использовать это как есть с Boost.Python, так как это совершенно нормальный указатель на функцию. Однако нам нужно решение, которое будет работать для любого вызываемого участника. Почему просто поддержка boost::function когда вы можете поддержать что-нибудь?

Начнем с @ Коломбоclosure_traits, но дополнительно добавив способ вытащить список аргументов;

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...>;                                        \
};

Затем мы напишем оболочку для любого вызываемого пользователя. Так как наша лямбда может не захватывать, мы должны принять вызываемое в качестве параметра шаблона:

template <typename CLS, typename F, F CLS::*callable>
class wrap { ... };

Я буду использовать C++14 auto возврат вычета типа, чтобы сохранить некоторую печать. Мы делаем на высшем уровне make_pointer() статическая функция-член, которая просто пересылает вспомогательную функцию-член, которая дополнительно принимает аргументы. Полный wrap похоже:

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...);
        };
    }
};

Что мы можем использовать, чтобы обернуть вашу кнопку:

void (*f)(gui_button_t*) = wrap<gui_button_t, 
                                decltype(gui_button_t::on_pressed),
                                &gui_button_t::on_pressed
                                >::make_pointer();

Это немного многословно и повторяется, поэтому давайте просто сделаем макрос (вздох):

#define WRAP_MEM(CLS, MEM) wrap<CLS, decltype(CLS::MEM), &CLS::MEM>::make_pointer()

Итак, мы получаем:

void (*f)(gui_button_t*) = WRAP_MEM(gui_button_t, on_pressed);

f(some_button); // calls some_button->on_pressed()

Поскольку это дает нам указатель на функцию, мы можем использовать это непосредственно с обычным API Boost.Python:

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

Демонстрация демонстрации указателей на функции для члена std::function и член struct с operator(),


Вышесказанное дает вам возможность выставить вызываемого. Если вы хотите дополнительно иметь возможность сделать назначение, то есть:

button = GuiButton()
button.on_pressed = callback_function
button.on_pressed()

Нам нужно сделать что-то еще. Вы не можете выставить operator= осмысленно в Python, поэтому для поддержки вышеуказанной функциональности вам придется переопределить __setattr__ вместо. Теперь, если вы были открыты для:

button.set_on_pressed(callback_function)

мы могли бы расширить вышеупомянутое wrap Решение добавить сеттер, реализация которого будет, в духе вышеизложенного:

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()

Который вы могли бы затем выставить через:

.def("set_on_pressed", SET_MEM(button, on_pressed))

Тем не менее, если вы настаиваете на поддержке прямого назначения, вам нужно будет дополнительно представить что-то вроде:

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);

Это бы сработало, но вам нужно было бы добавить больше случаев для каждого функтора, который вы хотите установить. Если у вас есть только один подобный функтору объект на класс, возможно, это не имеет большого значения, и вы даже можете написать функцию более высокого порядка для создания определенного setattr-подобная функция для данного имени атрибута. Но если у вас есть кратные, это будет постепенно ухудшаться, чем простой set_on_pressed решение.


Если C++14 недоступен, мы должны просто явно указать тип возвращаемого значения make_pointer, Нам понадобятся несколько удобных черт типа. 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...>;
};

А потом что-то, чтобы включить тип возврата и typelist в указатель на функцию:

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;

А потом в wrapмы можем просто определить тип результата как:

using R = make_fn_ptr_t<
                typename closure_traits<F>::result_type,
                concat_t<
                    typelist<CLS*>,
                    typename closure_traits<F>::args
                    >
                >;

и использовать это вместо auto, C++11 Демо.

Другие вопросы по тегам