当调用 swig 生成的函数返回 std::tuple
时,我得到了该 std::tuple
的 swig 对象。
有没有办法使用类型映射或其他东西来提取值?我尝试将一小部分代码更改为 std::vector ,效果很好。 (使用 %include
编辑:这是一个最小的可重现示例:
foo.h
#pragma once
#include <tuple>
class foo
{
private:
double secret1;
double secret2;
public:
foo();
~foo();
std::tuple<double, double> return_thing(void);
};
foo.cpp
#include "foo.h"
#include <tuple>
foo::foo()
{
secret1 = 1;
secret2 = 2;
}
foo::~foo()
{
}
std::tuple<double, double> foo::return_thing(void) {
return {secret1, secret2};
}
foo.i
%module foo
%{
#include"foo.h"
%}
%include "foo.h"
在我的 Linux 上使用编译时
-:$ swig -python -c++ -o foo_wrap.cpp foo.i
-:$ g++ -c foo.cpp foo_wrap.cpp '-I/usr/include/python3.8' '-fPIC' '-std=c++17' '-I/home/simon/Desktop/test_stack_overflow_tuple'
-:$ g++ -shared foo.o foo_wrap.o -o _foo.so
我可以将其导入到 python 中,如下所示: test_module.ipynb
import foo as f
Foo = f.foo()
return_object = Foo.return_thing()
type(return_object)
print(return_object)
输出是
SwigPyObject
<Swig Object of type 'std::tuple< double,double > *' at 0x7fb5845d8420>
希望这对您有帮助,谢谢您的回复
为了澄清我希望能够使用 python 中的值,如下所示:
main.cpp
#include "foo.h"
#include <iostream>
//------------------------------------------------------------------------------'
using namespace std;
int main()
{
foo Foo = foo();
auto [s1, s2] = Foo.return_thing();
cout << s1 << " " << s2 << endl;
}
//------------------------------------------------------------------------------
Github 存储库(如果有人感兴趣)
https://github.com/simon-cmyk/test_stack_overflow_tuple
最佳答案
我们的目标是让类似以下 SWIG 界面的东西直观地工作:
%module test
%include "std_tuple.i"
%std_tuple(TupleDD, double, double);
%inline %{
std::tuple<double, double> func() {
return std::make_tuple(0.0, 1.0);
}
%}
我们希望通过以下方式在 Python 中使用它:
import test
r=test.func()
print(r)
print(dir(r))
r[1]=1234
for x in r:
print(x)
即索引和迭代应该可以正常工作。
通过重复使用some of the pre-processor tricks I used to wrap std::function
(它们本身最初来自另一个 answer here on SO )我们可以定义一个简洁的宏,“只是包装” std::tuple
为了我们。虽然这个答案是 Python 特有的,但实际上它也应该相当简单,可以适应大多数其他语言。我将首先发布我的 std_tuple.i 文件,然后对其进行注释/解释:
// [1]
%{
#include <tuple>
#include <utility>
%}
// [2]
#define make_getter(pos, type) const type& get##pos() const { return std::get<pos>(*$self); }
#define make_setter(pos, type) void set##pos(const type& val) { std::get<pos>(*$self) = val; }
#define make_ctorargN(pos, type) , type v##pos
#define make_ctorarg(first, ...) const first& v0 FOR_EACH(make_ctorargN, __VA_ARGS__)
// [3]
#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
// [4]
%define %std_tuple(Name, ...)
%rename(Name) std::tuple<__VA_ARGS__>;
namespace std {
struct tuple<__VA_ARGS__> {
// [5]
tuple(make_ctorarg(__VA_ARGS__));
%extend {
// [6]
FOR_EACH(make_getter, __VA_ARGS__)
FOR_EACH(make_setter, __VA_ARGS__)
size_t __len__() const { return std::tuple_size<std::decay_t<decltype(*$self)>>{}; }
%pythoncode %{
# [7]
def __getitem__(self, n):
if n >= len(self): raise IndexError()
return getattr(self, 'get%d' % n)()
def __setitem__(self, n, val):
if n >= len(self): raise IndexError()
getattr(self, 'set%d' % n)(val)
%}
}
};
}
%enddef
- 这只是我们的宏正常工作所需的额外包含
- 这些适用于我们提供给
%std_tuple
的每个类型参数。宏调用时,我们需要小心这里的逗号以保持语法正确。 - 这就是我们
FOR_EACH
的机制宏,它调用可变参数宏参数列表中每个参数的每个操作 - 最后是
%std_tuple
的定义可以开始了。本质上,这是手动完成%template
的工作对于std::tuple
的每个专业我们关心 std 命名空间内的名称。 - 我们为每个魔法使用宏来为每个正确类型的元素声明一个带有参数的构造函数。这里的实际实现是 C++ 库中的默认实现,这正是我们需要/想要的。
- 我们使用我们的
FOR_EACH
宏两次以创建成员函数get0
,get1
,getN
每个元组元素的正确类型以及模板参数大小的正确数量。setN
同样如此。通过这种方式,可以使用double
的常用 SWIG 类型映射。等或您的元组包含的任何类型将自动正确地应用于每次调用std::get<N>
。这些实际上只是一个实现细节,并不打算成为公共(public)接口(interface)的一部分,但公开它们并没有什么实际意义。 - 最后我们需要实现
__getitem__
和相应的__setitem__
。这些只需查找并调用右侧的getN
/setN
类上的函数并调用它。我们小心筹集IndexError
而不是使用无效索引时的默认异常,因为当我们尝试迭代元组时,这将正确停止迭代。
这足以让我们运行目标代码并获得以下输出:
$ swig3.0 -python -c++ -Wall test.i && g++ -shared -o _test.so test_wrap.cxx -I/usr/include/python3.7 -m32 && python3.7 run.py
<test.TupleDD; proxy of <Swig Object of type 'std::tuple< double,double > *' at 0xf766a260> >
['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'get0', 'get1', 'set0', 'set1', 'this']
0.0
1234.0
通常,在 Python 的大多数输入/输出情况下,这应该可以如您所希望的那样工作。
我们可以进行一些改进:
- 实现代表
- 实现切片,以便
tuple[n:m]
类型索引工作 - 像 Python 元组一样处理解包。
- 也许可以对兼容类型进行更多自动转换?
- 避免调用
__len__
对于每个 get/setitem 调用,要么通过在类本身中缓存值,要么推迟它直到方法查找失败?
关于swig - 在 swig 中支持 std::tuple 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72816953/