Boost.Python 只接受函数指针和成员函数指针。所以我们需要做的就是将我们的可调用对象转换为函数指针。这里的关键想法是
- 没有捕获的 lambda 可以转换为函数指针(通过sorcery https://stackoverflow.com/q/18889028/2069064)
- 函数指针的解释方式与 Python 中的成员函数相同:第一个参数是
self
所以在你的例子中,我们需要做的是生成这个 lambda:
+[](gui_button_t* self) {
self->on_pressed();
}
您已经可以在 Boost.Python 中按原样使用它,因为这是一个完全正常的函数指针。但是,我们想要一个适用于以下情况的解决方案any可调用的成员。为什么just支持boost::function
什么时候你可以支持任何事情?
我们将从@科伦坡的closure_traits https://stackoverflow.com/a/28213747/2069064,但还添加了一种提取参数列表的方法;
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...>; \
};
然后我们将为任何可调用成员编写一个包装器。因为我们的 lambda 不能捕获,所以我们have将可调用对象作为模板参数:
template <typename CLS, typename F, F CLS::*callable>
class wrap { ... };
我将使用 C++14auto
返回类型推导以节省一些打字。我们做的是顶级的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()
由于这为我们提供了一个函数指针,因此我们可以直接将其与普通的 Boost.Python API 一起使用:
class_<gui_button_t>("GuiButton", init<>())
.def("on_pressed", WRAP_MEM(gui_button_t, on_pressed));
Demo http://coliru.stacked-crooked.com/a/939cb57f70c8e42b演示指向成员的函数指针std::function
和一名会员struct
与operator()
.
上述内容使您能够expose一个可调用的。如果您还想能够进行作业,即:
button = GuiButton()
button.on_pressed = callback_function
button.on_pressed()
我们需要做点别的事情。你不能暴露operator=
在Python中以一种有意义的方式,所以为了支持上述功能,你必须重写__setattr__
反而。现在,如果您愿意:
button.set_on_pressed(callback_function)
我们可以扩展上面的内容wrap
添加 setter 的解决方案,其实现方式与上述类似:
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 演示 http://coliru.stacked-crooked.com/a/d92e2289d6acd922.