Reputation: 5772
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
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