user3612643
user3612643

Reputation: 5772

Simple stream iterator in C++

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:

Upvotes: 1

Views: 986

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275310

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.

Upvotes: 1

Related Questions