No iterator for Java when using SWIG with C++'s std::map
Asked Answered
P

1

8

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?

Peduncle answered 27/2, 2012 at 13:31 Comment(3)
You'll need to precise what you mean by not "all" entries. Anything specific, like the last item missing ? Sharing some code to show us how you did the interop ?Breakwater
Sorry, to be precise I am unable to perform any iteration at all.Peduncle
A quick Google Search found this: chadretz.wordpress.com/2009/11/27/… Maybe it helpsNecking
L
12

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.

Landre answered 28/2, 2012 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.