如何将 class 成员方法的参数列表自动填充到可变参数中?
How to write auto-populate the parameter list of a class member method into a Variadic arguments?
最近用C++写了一个Python3的扩展,但是在python中调用C++的时候遇到了一些麻烦。
不知道如何封装下面的代码,而不需要每次都重复写回调函数?
我正在考虑以某种形式绑定回调函数和参数列表,但我不知道该怎么做。
这是我的主要代码:
class TestClass
{
PyObject_HEAD
public:
int add(int a, int b) {
return (a + b);
}
};
// ... Ignore some details here ...
static PyObject* function1(TestClass *self, PyObject* args) {
// How to changed the following code to take the value from PyArg_ParseTuple
// through the binding function and parameter list?
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
{
return nullptr;
}
// How to changed the following code to the return type of the binding function?
return Py_BuildValue("i", self->add(a, b));
}
是否可以通过某种方式实现像BINDING_FUNCTION(TestClass::add);
这样的调用?
是的,这是可能的,但它需要大量的代码。我认为太多了,无法轻松地在此处给出答案。
好消息是其他人已经编写了您需要的代码并将它们放入库中。我认识的有pybind11 (modern) and boost.python(年纪大一些)
您可以看看这些库是如何做到这一点的,或者实际使用这些库。
如果您真的想自己做,大约需要 个步骤:
- 创建通用函数以从函数参数类型和 return 类型创建格式字符串
- 创建模板元函数来定义元组类型以保存给定(成员)函数的参数
- 实现可以使用
PyArg_ParseTuple()
填充此类元组的通用函数。这将使用格式字符串生成函数和元组定义
- 实现一个通用函数,它接受一个对象、一个(成员)函数指针和一个带参数的元组,并将调用带给定参数的成员函数。所以 has answers 如何做到这一点。
通常,您希望将最后一个函数专门化为 void return 类型函数,如果除了成员函数之外还想支持自由函数,您可能需要编写一些额外的代码。
以下代码仅用于教育目的,因为它是上述步骤的非常简化的实现。前面提到的库实现了此代码未解决的许多极端情况。
#include <iostream>
#include <sstream>
#include <type_traits>
#include <tuple>
// some object we want to wrap
struct Duck
{
int MemberFunc( int x, int y, float w)
{
std::cout << "Member function called\n";
return x+ w * y;
}
};
// PART 1: create format strings for function argument- and return types
// "excercise for the reader": implement these overloads for all supported types
template<typename T> struct Tag{};
const char *GetTypeFormat( const Tag<int>&)
{
return "i";
}
const char *GetTypeFormat( const Tag<float>&)
{
return "f";
}
// create a format string from a list of argument types
template< typename... Args>
void GetTypeFormats( std::ostream &strm)
{
(void)(int[]){0, ((strm << GetTypeFormat(Tag<Args>{})),0)...};
}
// this is quite inefficient because it creates the format string at
// run-time. Doing this as constexpr is an interesting challenge
// ("...for the reader")
template< typename R, typename Class, typename... Args>
std::string GetArgumentFormats( R (Class::*f)(Args...))
{
std::stringstream strm;
GetTypeFormats<Args...>( strm);
return strm.str();
}
template< typename R, typename Class, typename... Args>
std::string GetReturnFormat( R (Class::*f)(Args...))
{
std::stringstream strm;
GetTypeFormats<R>( strm);
return strm.str();
}
// PART 2: declare std::tuple-type to hold function arguments
// given a list of types that could be function parameter types, define a
// tuple type that can hold all argument values to such a function
// THIS IS VERY MUCH A SIMPLIFIED IMPLEMENTATION
// This doesn't take pointer types into account for instance.
template< typename F>
struct ArgumentTuple {};
template< typename R, typename Class, typename... Args>
struct ArgumentTuple<R (Class::*)( Args...)>
{
using type = std::tuple<
typename std::remove_cv<
typename std::remove_reference<Args>::type>::type...>;
};
// for demo purposes. emulate python binding functions
using PyObject = void;
bool PyArg_ParseTuple( PyObject *, const char *, ...) {}
template< typename T>
PyObject *Py_BuildValue( const char*, T ){}
// PART 3: given some function pointer, obtain arguments from a PyObject
template<typename F, size_t... Indexes>
auto FillTuple( PyObject *obj, F f, std::index_sequence<Indexes...>) -> typename ArgumentTuple<F>::type
{
using std::get;
typename ArgumentTuple<F>::type arguments;
// no error checking whatsoever: "exercise for the reader"
PyArg_ParseTuple( obj, GetArgumentFormats( f).c_str(), &get<Indexes>( arguments)...);
return arguments;
}
template< typename R, typename Class, typename... Args>
auto FillTuple( PyObject *obj, R (Class::*f)(Args...))
{
return FillTuple( obj, f, std::index_sequence_for<Args...>{});
}
// PART 4, call a member function given a tuple of arguments
// helper function
template<
typename R,
typename Class,
typename MF,
typename ArgumentTuple,
size_t... Indexes>
R Apply( Class &obj, MF f, ArgumentTuple &args, std::index_sequence<Indexes...>)
{
using std::get;
return (obj.*f)( get<Indexes>( args)...);
}
// Apply a (member-) function to a tuple of arguments.
template<
typename R,
typename Class,
typename ArgumentTuple,
typename... Args>
R Apply( Class &obj, R (Class::*f)( Args...), ArgumentTuple &args)
{
return Apply<R>( obj, f, args, std::index_sequence_for<Args...>{});
}
// LAST PART: glue everything together in a single function.
#define BIND_MEMBER_FUNCTION( class_, memberfunc_) \
PyObject *Call##class_##memberfunc_( class_ *self, PyObject *args)\
{ \
/* no error checking whatsoever: "exercise for the reader"*/\
auto arguments = FillTuple( args, &class_::memberfunc_); \
/* deal with void-returning functions: yet another EFTR */ \
return Py_BuildValue( \
GetReturnFormat( &class_::memberfunc_).c_str(), \
Apply( *self, &class_::memberfunc_, arguments)); \
} \
/**/
BIND_MEMBER_FUNCTION( Duck, MemberFunc);
最近用C++写了一个Python3的扩展,但是在python中调用C++的时候遇到了一些麻烦。
不知道如何封装下面的代码,而不需要每次都重复写回调函数?
我正在考虑以某种形式绑定回调函数和参数列表,但我不知道该怎么做。
这是我的主要代码:
class TestClass
{
PyObject_HEAD
public:
int add(int a, int b) {
return (a + b);
}
};
// ... Ignore some details here ...
static PyObject* function1(TestClass *self, PyObject* args) {
// How to changed the following code to take the value from PyArg_ParseTuple
// through the binding function and parameter list?
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
{
return nullptr;
}
// How to changed the following code to the return type of the binding function?
return Py_BuildValue("i", self->add(a, b));
}
是否可以通过某种方式实现像BINDING_FUNCTION(TestClass::add);
这样的调用?
是的,这是可能的,但它需要大量的代码。我认为太多了,无法轻松地在此处给出答案。
好消息是其他人已经编写了您需要的代码并将它们放入库中。我认识的有pybind11 (modern) and boost.python(年纪大一些)
您可以看看这些库是如何做到这一点的,或者实际使用这些库。
如果您真的想自己做,大约需要 个步骤:
- 创建通用函数以从函数参数类型和 return 类型创建格式字符串
- 创建模板元函数来定义元组类型以保存给定(成员)函数的参数
- 实现可以使用
PyArg_ParseTuple()
填充此类元组的通用函数。这将使用格式字符串生成函数和元组定义 - 实现一个通用函数,它接受一个对象、一个(成员)函数指针和一个带参数的元组,并将调用带给定参数的成员函数。所以 has answers 如何做到这一点。
通常,您希望将最后一个函数专门化为 void return 类型函数,如果除了成员函数之外还想支持自由函数,您可能需要编写一些额外的代码。
以下代码仅用于教育目的,因为它是上述步骤的非常简化的实现。前面提到的库实现了此代码未解决的许多极端情况。
#include <iostream>
#include <sstream>
#include <type_traits>
#include <tuple>
// some object we want to wrap
struct Duck
{
int MemberFunc( int x, int y, float w)
{
std::cout << "Member function called\n";
return x+ w * y;
}
};
// PART 1: create format strings for function argument- and return types
// "excercise for the reader": implement these overloads for all supported types
template<typename T> struct Tag{};
const char *GetTypeFormat( const Tag<int>&)
{
return "i";
}
const char *GetTypeFormat( const Tag<float>&)
{
return "f";
}
// create a format string from a list of argument types
template< typename... Args>
void GetTypeFormats( std::ostream &strm)
{
(void)(int[]){0, ((strm << GetTypeFormat(Tag<Args>{})),0)...};
}
// this is quite inefficient because it creates the format string at
// run-time. Doing this as constexpr is an interesting challenge
// ("...for the reader")
template< typename R, typename Class, typename... Args>
std::string GetArgumentFormats( R (Class::*f)(Args...))
{
std::stringstream strm;
GetTypeFormats<Args...>( strm);
return strm.str();
}
template< typename R, typename Class, typename... Args>
std::string GetReturnFormat( R (Class::*f)(Args...))
{
std::stringstream strm;
GetTypeFormats<R>( strm);
return strm.str();
}
// PART 2: declare std::tuple-type to hold function arguments
// given a list of types that could be function parameter types, define a
// tuple type that can hold all argument values to such a function
// THIS IS VERY MUCH A SIMPLIFIED IMPLEMENTATION
// This doesn't take pointer types into account for instance.
template< typename F>
struct ArgumentTuple {};
template< typename R, typename Class, typename... Args>
struct ArgumentTuple<R (Class::*)( Args...)>
{
using type = std::tuple<
typename std::remove_cv<
typename std::remove_reference<Args>::type>::type...>;
};
// for demo purposes. emulate python binding functions
using PyObject = void;
bool PyArg_ParseTuple( PyObject *, const char *, ...) {}
template< typename T>
PyObject *Py_BuildValue( const char*, T ){}
// PART 3: given some function pointer, obtain arguments from a PyObject
template<typename F, size_t... Indexes>
auto FillTuple( PyObject *obj, F f, std::index_sequence<Indexes...>) -> typename ArgumentTuple<F>::type
{
using std::get;
typename ArgumentTuple<F>::type arguments;
// no error checking whatsoever: "exercise for the reader"
PyArg_ParseTuple( obj, GetArgumentFormats( f).c_str(), &get<Indexes>( arguments)...);
return arguments;
}
template< typename R, typename Class, typename... Args>
auto FillTuple( PyObject *obj, R (Class::*f)(Args...))
{
return FillTuple( obj, f, std::index_sequence_for<Args...>{});
}
// PART 4, call a member function given a tuple of arguments
// helper function
template<
typename R,
typename Class,
typename MF,
typename ArgumentTuple,
size_t... Indexes>
R Apply( Class &obj, MF f, ArgumentTuple &args, std::index_sequence<Indexes...>)
{
using std::get;
return (obj.*f)( get<Indexes>( args)...);
}
// Apply a (member-) function to a tuple of arguments.
template<
typename R,
typename Class,
typename ArgumentTuple,
typename... Args>
R Apply( Class &obj, R (Class::*f)( Args...), ArgumentTuple &args)
{
return Apply<R>( obj, f, args, std::index_sequence_for<Args...>{});
}
// LAST PART: glue everything together in a single function.
#define BIND_MEMBER_FUNCTION( class_, memberfunc_) \
PyObject *Call##class_##memberfunc_( class_ *self, PyObject *args)\
{ \
/* no error checking whatsoever: "exercise for the reader"*/\
auto arguments = FillTuple( args, &class_::memberfunc_); \
/* deal with void-returning functions: yet another EFTR */ \
return Py_BuildValue( \
GetReturnFormat( &class_::memberfunc_).c_str(), \
Apply( *self, &class_::memberfunc_, arguments)); \
} \
/**/
BIND_MEMBER_FUNCTION( Duck, MemberFunc);