I'm wrapping a class using SWIG typemaps, and a std::vector
of this class using "std_vector.i" provided by SWIG. The target language is Python. Everything seems to work except that I cannot iterate over the vector.
I've created a minimal example about complex numbers (for convenience only, it has nothing to do with my real project).
This is the C++ class I'd like to wrap:
struct Komplex
{
Komplex() : real(0.0), imag(0.0) {}
double real;
double imag;
};
This is my SWIG interface file:
%module myproject
%{
#include "komplex.h"
%}
%typemap(typecheck) Komplex&
{
PyComplex_Check($input) ? 1 : 0;
}
%typemap(in) Komplex& (Komplex temp)
{
temp.real = PyComplex_RealAsDouble($input);
temp.imag = PyComplex_ImagAsDouble($input);
$1 = &temp;
}
%typemap(out) Komplex&
{
$result = PyComplex_FromDoubles($1->real, $1->imag);
}
// define a vector of the wrapped class:
%include "std_vector.i"
%template(KomplexVector) std::vector<Komplex>;
I can simply test the typecheck/in/out typemaps using the following Python code:
import myproject
# fill a vector (the "typecheck" and "in" typemaps are used!)
vec = myproject.KomplexVector()
vec.append( complex(1,2) )
# below, the output typemap is used:
print("First attempt:")
for i in xrange(len(vec)):
print(vec[i]) # prints: (1+2j)
assert vec[i] == complex(1,2) # OK
# below, the output typemap is NOT used:
print("Second attempt:")
for k in vec:
print(k) # prints: <Swig Object of type 'Komplex *' at 0x7f0194c6de10>
assert k == complex(1,2) # fails!
As you can see in the output, iterating over the vector results in an opaque value:
First attempt:
(1+2j)
Second attempt:
<Swig Object of type 'Komplex *' at 0x7f0194c6de10>
Traceback (most recent call last):
File "test.py", line 17, in <module>
assert k == complex(1,2) # fails!
AssertionError
swig/python detected a memory leak of type 'Komplex *', no destructor found.
So my question is simple: how can I get it to work?
I've tried the following:
- brute force, i.e. defining all possibly relevant typemaps I can imagine:
- more in/out/varout/... typemaps for
Komplex
values/pointers/references/const references/... - defining in/out/varout/... typemaps for
std::vector<Komplex>::value_type
values/pointers/references/.. - same for
std::vector<Komplex>::difference_type
/iterator
/const_iterator
/ ...
- more in/out/varout/... typemaps for
- using the latest SWIG version from github
- copying the approach from here
- a bunch of other things, like changing the order of the contents in the interface file
... all to no avail.
The full source code (including a little build script for Linux/g++) can be found here.
Any help appreciated!
Edit:
There are a few workarounds that avoid the lack of a properly wrapped iterator, but none of them are pretty (I think). E.g. one possibility is to define a custom Iterator object in Python, and let SWIG modify the iterator
and insert
methods in the Python code.
For instance, the following code will make sure that for k in vec: ...
and vec.insert(1, complex(4,5))
will produce the expected behavior:
%pythoncode %{
class MyVectorIterator(object):
def __init__(self, pointerToVector):
self.pointerToVector = pointerToVector
self.index = -1
def next(self):
self.index += 1
if self.index < len(self.pointerToVector):
return self.pointerToVector[self.index]
else:
raise StopIteration
%}
%rename(__cpp_iterator) std::vector<Komplex>::iterator;
%rename(__cpp_insert) std::vector<Komplex>::insert;
%extend std::vector<Komplex> {
%pythoncode {
def iterator(self):
return MyVectorIterator(self)
def insert(self, i, x):
if isinstance(i, int): # "insert" is used as if the vector is a Python list
_myproject.KomplexVector___cpp_insert(self, self.begin() + i, x)
else: # "insert" is used as if the vector is a native C++ container
return _myproject.KomplexVector___cpp_insert(self, i, x)
}
}
%include "std_vector.i"
%template(KomplexVector) std::vector<Komplex>;
I wouldn't call it a "solution" (let alone a "nice" solution) but it can serve as a temporary workaround I guess...