I have implemented a class with std::map
in C++ and created interface using SWIG to be call from Java. However there is no iterator object that allows me to iterate through the entries in the SWIG wrapped std::map
. Does anyone know how to create an iterator?
In order to be able to iterate over an Object in Java it needs to implement Iterable
. This in turn requires a member function called iterator()
which returns a suitable implementation of an Iterator
.
From your question it's not quite clear if what types you're using in the map and if you want to be able to iterate over the pairs (as you would in C++), the keys or the values. The solutions to the three variants are substantially similar, my example below picked the values.
First things first, the preamble for the SWIG interface file I used to test this:
%module test
%include "std_string.i"
%include "std_map.i"
In order to implement the iterable map I've declared, defined and wrapped another class in the SWIG interface file. This class, MapIterator
implements the Iterator
interface for us. It's a mixture of both Java and wrapped C++, where one was easier than the other to write. Firstly some Java, a typemap that gives the interface it implements and then two of the three methods required for the Iterable
interface, given as a typemap:
%typemap(javainterfaces) MapIterator "java.util.Iterator<String>"
%typemap(javacode) MapIterator %{
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public String next() throws java.util.NoSuchElementException {
if (!hasNext()) {
throw new java.util.NoSuchElementException();
}
return nextImpl();
}
%}
Then we supply the C++ part of MapIterator
, which has a private implementation of all but the exception throwing part of next()
and the state needed for the iterator (expressed in terms of std::map
's own const_iterator
).
%javamethodmodifiers MapIterator::nextImpl "private";
%inline %{
struct MapIterator {
typedef std::map<int,std::string> map_t;
MapIterator(const map_t& m) : it(m.begin()), map(m) {}
bool hasNext() const {
return it != map.end();
}
const std::string& nextImpl() {
const std::pair<int,std::string>& ret = *it++;
return ret.second;
}
private:
map_t::const_iterator it;
const map_t& map;
};
%}
Finally we need to tell SWIG that the std::map
we're wrapping implements the Iterable
interface and provide an extra member function for the purposes of wrapping std::map
which returns a new instance of the MapIterator
class we just wrote:
%typemap(javainterfaces) std::map<int,std::string> "Iterable<String>"
%newobject std::map<int,std::string>::iterator() const;
%extend std::map<int,std::string> {
MapIterator *iterator() const {
return new MapIterator(*$self);
}
}
%template(MyMap) std::map<int,std::string>;
This could be more generic, with macros for example to hide the types of the map such that if you have multiple maps it's just a question of "calling" the macro for the appropriate maps just like you do with %template
.
There's also a slight complication with maps of primitive types - you'll need to arrange for the Java side to use Double
/Integer
instead of double
/int
(autoboxing I believe is the term), unless you decided to wrap pairs already in which case you could make a pair with primitive members.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With