如何使用 SWIG 包装 C++17 std::variant 以便在 python 中使用?
How to wrap C++17 std::variant with SWIG for use in python?
我正在尝试使用 SWIG 将一些使用 std::variant.
的 C++17 代码包装在 Python 中
我找到了这个关于包装 boost::variant 的答案 (),并且我设法调整了代码,使其适用于 std::variant。但是,根据答案的规范,代码应该可以工作 'anywhere a C++ function takes a boost::variant we should transparently accept any of the types the variant can hold for that function argument'。这个要求似乎只有在使用 std::variant 的 const 引用时才能满足。例如,如果在我的 C++ 文件中 dummy.cpp 我有
void foo(std::variant<int, double> value)
{
std::cout << "foo" << std::endl;
}
void bar(const std::variant<int, double>& value)
{
std::cout << "bar" << std::endl;
}
然后在我的 dummy.i
%include "variant.i" # My customization of boost_variant.i
%std_variant(DummyVariant, int, double); # Creating the variant type for SWIG/python
当我尝试从 Python 脚本中使用它时,以下工作正常
from dummy import bar
bar(5)
然而,这不是:
from dummy import foo
foo(5)
我得到:类型错误:在方法 'foo' 中,参数 1 类型 'std::variant< int,double >'。
是否有人知道我的 variant.i 文件中缺少什么才能使它按预期工作?这是我的文件:
%{
#include <variant>
static PyObject *this_module = NULL;
%}
%init %{
// We need to "borrow" a reference to this for our typemaps to be able to look up the right functions
this_module = m; // borrow should be fine since we can only get called when our module is loaded right?
// Wouldn't it be nice if $module worked *anywhere*
%}
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1); action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1); action(1,a2); action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1); action(1,a2); action(2,a3); action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1); action(1,a2); action(2,a3); action(3,a4); action(4,a5)
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef
#define in_helper(num,type) const type & convert_type ## num () { return std::get<type>(*$self); }
#define constructor_helper(num,type) variant(const type&)
%define %std_variant(Name, ...)
%rename(Name) std::variant<__VA_ARGS__>;
namespace std {
struct variant<__VA_ARGS__> {
variant();
variant(const std::variant<__VA_ARGS__>&);
FOR_EACH(constructor_helper, __VA_ARGS__);
int index();
%extend {
FOR_EACH(in_helper, __VA_ARGS__);
}
};
}
%typemap(out) std::variant<__VA_ARGS__> {
// Make our function output into a PyObject
PyObject *tmp = SWIG_NewPointerObj(&, $&1_descriptor, 0); // Python does not own this object...
// Pass that temporary PyObject into the helper function and get another PyObject back in exchange
const std::string func_name = "convert_type" + std::to_string(.index());
$result = PyObject_CallMethod(tmp, func_name.c_str(), "");
Py_DECREF(tmp);
}
%typemap(in) const std::variant<__VA_ARGS__>& (PyObject *tmp=NULL) {
// I don't much like having to "guess" the name of the make_variant we want to use here like this...
// But it's hard to support both -builtin and regular modes and generically find the right code.
PyObject *helper_func = PyObject_GetAttrString(this_module, "new_" #Name );
assert(helper_func);
// TODO: is O right, or should it be N?
tmp = PyObject_CallFunction(helper_func, "O", $input);
Py_DECREF(helper_func);
if (!tmp) SWIG_fail; // An exception is already pending
// TODO: if we cared, we chould short-circuit things a lot for the case where our input really was a variant object
const int res = SWIG_ConvertPtr(tmp, (void**)&, _descriptor, 0);
if (!SWIG_IsOK(res)) {
SWIG_exception_fail(SWIG_ArgError(res), "Variant typemap failed, not sure if this can actually happen");
}
}
%typemap(freearg) const std::variant<__VA_ARGS__>& %{
Py_DECREF(tmp$argnum);
%}
%enddef
几乎逐字 来自 Flexo 的回答,保存一些更改以使其适用于 std::variant 而不是 boost::variant。
避免在界面上使用模板并创建应用程序二进制界面 (ABI) 的最简单方法。在您的情况下,这对应于创建一个接口 dummy.i
%module dummy
%{
#include "dummy.h"
void mywrap_foo_int(int i) {
std::variant<int, double> tmp;
tmp = i;
foo(tmp);
}
void mywrap_foo_double(double d) {
std::variant<int, double> tmp;
tmp = d;
foo(tmp);
}
%}
%include "variant.i"
%std_variant(DummyVariant, int, double);
%include "dummy.h"
很快,你就会遇到模板和嵌套模板。这可以像您已经完成的那样以通用方式完成,它处理 bar
的签名。包裹的任务基本对应于此。
我正在尝试使用 SWIG 将一些使用 std::variant.
的 C++17 代码包装在 Python 中我找到了这个关于包装 boost::variant 的答案 (),并且我设法调整了代码,使其适用于 std::variant。但是,根据答案的规范,代码应该可以工作 'anywhere a C++ function takes a boost::variant we should transparently accept any of the types the variant can hold for that function argument'。这个要求似乎只有在使用 std::variant 的 const 引用时才能满足。例如,如果在我的 C++ 文件中 dummy.cpp 我有
void foo(std::variant<int, double> value)
{
std::cout << "foo" << std::endl;
}
void bar(const std::variant<int, double>& value)
{
std::cout << "bar" << std::endl;
}
然后在我的 dummy.i
%include "variant.i" # My customization of boost_variant.i
%std_variant(DummyVariant, int, double); # Creating the variant type for SWIG/python
当我尝试从 Python 脚本中使用它时,以下工作正常
from dummy import bar
bar(5)
然而,这不是:
from dummy import foo
foo(5)
我得到:类型错误:在方法 'foo' 中,参数 1 类型 'std::variant< int,double >'。
是否有人知道我的 variant.i 文件中缺少什么才能使它按预期工作?这是我的文件:
%{
#include <variant>
static PyObject *this_module = NULL;
%}
%init %{
// We need to "borrow" a reference to this for our typemaps to be able to look up the right functions
this_module = m; // borrow should be fine since we can only get called when our module is loaded right?
// Wouldn't it be nice if $module worked *anywhere*
%}
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1); action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1); action(1,a2); action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1); action(1,a2); action(2,a3); action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1); action(1,a2); action(2,a3); action(3,a4); action(4,a5)
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef
#define in_helper(num,type) const type & convert_type ## num () { return std::get<type>(*$self); }
#define constructor_helper(num,type) variant(const type&)
%define %std_variant(Name, ...)
%rename(Name) std::variant<__VA_ARGS__>;
namespace std {
struct variant<__VA_ARGS__> {
variant();
variant(const std::variant<__VA_ARGS__>&);
FOR_EACH(constructor_helper, __VA_ARGS__);
int index();
%extend {
FOR_EACH(in_helper, __VA_ARGS__);
}
};
}
%typemap(out) std::variant<__VA_ARGS__> {
// Make our function output into a PyObject
PyObject *tmp = SWIG_NewPointerObj(&, $&1_descriptor, 0); // Python does not own this object...
// Pass that temporary PyObject into the helper function and get another PyObject back in exchange
const std::string func_name = "convert_type" + std::to_string(.index());
$result = PyObject_CallMethod(tmp, func_name.c_str(), "");
Py_DECREF(tmp);
}
%typemap(in) const std::variant<__VA_ARGS__>& (PyObject *tmp=NULL) {
// I don't much like having to "guess" the name of the make_variant we want to use here like this...
// But it's hard to support both -builtin and regular modes and generically find the right code.
PyObject *helper_func = PyObject_GetAttrString(this_module, "new_" #Name );
assert(helper_func);
// TODO: is O right, or should it be N?
tmp = PyObject_CallFunction(helper_func, "O", $input);
Py_DECREF(helper_func);
if (!tmp) SWIG_fail; // An exception is already pending
// TODO: if we cared, we chould short-circuit things a lot for the case where our input really was a variant object
const int res = SWIG_ConvertPtr(tmp, (void**)&, _descriptor, 0);
if (!SWIG_IsOK(res)) {
SWIG_exception_fail(SWIG_ArgError(res), "Variant typemap failed, not sure if this can actually happen");
}
}
%typemap(freearg) const std::variant<__VA_ARGS__>& %{
Py_DECREF(tmp$argnum);
%}
%enddef
几乎逐字 来自 Flexo 的回答,保存一些更改以使其适用于 std::variant 而不是 boost::variant。
避免在界面上使用模板并创建应用程序二进制界面 (ABI) 的最简单方法。在您的情况下,这对应于创建一个接口 dummy.i
%module dummy
%{
#include "dummy.h"
void mywrap_foo_int(int i) {
std::variant<int, double> tmp;
tmp = i;
foo(tmp);
}
void mywrap_foo_double(double d) {
std::variant<int, double> tmp;
tmp = d;
foo(tmp);
}
%}
%include "variant.i"
%std_variant(DummyVariant, int, double);
%include "dummy.h"
很快,你就会遇到模板和嵌套模板。这可以像您已经完成的那样以通用方式完成,它处理 bar
的签名。包裹的任务基本对应于此。