1

Consider a Java-like streaming iterator:

template<class V>
struct Iterator
{
  V& next();
  bool hasNext();
}

template<class V>
struct Iterable
{
    Iterator<V> iterator();
}

How would you cast this into a std::iterator so that you can use it in find, for-loops, etc.

I think I need to start with an InputIterator, but I am struggling with the following things here:

  • meaningful end()?
  • when are they equal()?
  • what about Distance?
user3612643
  • 5,096
  • 7
  • 34
  • 55

1 Answers1

1

It is quite doable, but a pain.

You would be better off doing a generator-based iterator, where the backing operation is a std::function< optional<T> >. Here is an example of such a generator-iterator:

template<class T>
struct generator_iterator {
  using difference_type=std::ptrdiff_t;
  using value_type=T;
  using pointer=T*;
  using reference=T;
  using iterator_category=std::input_iterator_tag;
  std::optional<T> state;
  std::function< std::optional<T>() > operation;
  // we store the current element in "state" if we have one:
  T operator*() const {
    return *state;
  }
  // to advance, we invoke our operation.  If it returns a nullopt
  // we have reached the end:
  generator_iterator& operator++() {
    state = operation();
    return *this;        
  }
  generator_iterator operator++(int) {
    auto r = *this;
    ++(*this);
    return r;
  }
  // generator iterators are only equal if they are both in the "end" state:
  friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) {
    if (!lhs.state && !rhs.state) return true;
    return false;
  }
  friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) {
    return !(lhs==rhs);
  }
  // We implicitly construct from a std::function with the right signature:
  generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))
  {
    if (operation)
      state = operation();
  }
  // default all special member functions:
  generator_iterator( generator_iterator && ) =default;
  generator_iterator( generator_iterator const& ) =default;
  generator_iterator& operator=( generator_iterator && ) =default;
  generator_iterator& operator=( generator_iterator const& ) =default;
  generator_iterator() =default;
};

Doing so with your Java-like iterface can still be done.

There are going to be solutions in boost that make this easier. But I'll write it "in the raw" based off C++17 concepts, which can be backported to C++11 if you need them (or extracted from boost or other sources).

You'd be generating an input iterator, because that is what Java-like interface supports.

First I'd write a helper. The helper would hold a optional< Iterator<V> > and an optional<V>.

It would support ++. ++ would advance the iterator and read the value into the optional<V>.

It would support unary *. * would return the value in the optional.

bool is_end() returns true if the optional<Iterator<V>> is empty, or if it !.hasNext().

== returns true if and only if both arguments .is_end(). != would just be ! applied to ==.

This helper isn't yet an iterator, but it has most of the key operations.

Then we use this poly_iterator which type erases anything iterator-like. The operations we wrote above on the helper are sufficient.

Then we write a function that takes a Iterable<V> and produces a range<poly_iterator<T>> of the type erasure class above, using the helper pseudo-iterator above. A range<It> is a class that looks like:

template<class It>
struct range {
  It b; It e;
  It begin() const { return b; }
  It end() const { return e; }
};

plus maybe other utility helpers.

The type erasure step could be skipped if you chose. Just augment the helper written above to be a full-fledged input iterator. Boost has helpers that make this a touch easier. Have the range-returner return that full-fledged input iterator. I happened to have the poly_iterator<T> lying around in another answer, and if you have a hammer that problem sure looks like a nail.

Note that for the code to be really Java-like, we'd want to actually have (smart) pointers to the objects not copies.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Okay, I was just stumbling over the == operator. So, you say that two iterators are only equal if they both are at end. That makes sense since otherwise it's hard to define equality here because the Iterable could mutate between two calls. – user3612643 Sep 28 '16 at 15:08
  • @user3612643 Yes, two iterators are only equal if they are both at the end. This is a model of input iteration. And the end iterator is either an iterator that has no `hasNext` or is a sentinal. Constructing the helper from your java-esque iterator first asks `hasNext`, if not it is an end iterator, otherwise it stores the `next` element immediately. `++` checks `hasNext`, if not it ends itself, otherwise it stores the `next`. You could probably defer the call to `next` until actually read, but I think that makes it a bit harder to write, so I will do it at construction and at `++`. – Yakk - Adam Nevraumont Sep 28 '16 at 15:34
  • The java-style model above was only an example. Actually, what I implemented is a two-level function wrapper: using Next = std::function – user3612643 Sep 28 '16 at 16:12
  • @user3612643 So you want generator-backed iterators, not wrapping Java-like iterators. There will be subtle differences, but not many. Generator-backed iterators are available from `boost`. The fact that has next and next are *the same operation* makes the code simpler, and if you are an input iterator you are permitted to ignore `++` and advance on `*`. And the fact you are already type-erasing makes type-erasure pointless. Had you asked your real question and not a fake one, I would have answered it with a better answer, in short. – Yakk - Adam Nevraumont Sep 28 '16 at 16:38
  • the question wasn't fake, but rather reflected my thinking of what I want to achieve at that point in time. With your answer, I could "rephrase" the problem statement. – user3612643 Sep 28 '16 at 17:06
  • @user3612643 [here](https://stackoverflow.com/documentation/c%2b%2b/473/iterators/23826/write-your-own-generator-backed-iterator#t=201609281731232441088) is an example in documentation that writes a generator-backed input iterator. I think I made it standards compliant. – Yakk - Adam Nevraumont Sep 28 '16 at 17:32