I'm currently using a third-party library which contains a class that only provides indexed lookup, i.e. operator[]
.
I'd like to do a range-based for loop on this class's contents. However, having never written an iterator or iterator adapter, I'm quite lost. It seems that writing iterators is not a straightforward task.
My desired usage is:
for(auto element : container)
{
...
}
Instead of having to write:
for(int i = 0; i < container.size(); ++i)
{
auto element = container[i];
...
}
How can this be achieved? Does Boost provide this functionality?
Writing iterators is actually a rather straightforward task, but it gets extremely tedious. Since your container supports indexing by an integer, I assume its iterators would fall into the random access iterator category (if they existed). That needs a lot of boilerplate!
However, to support the range-based for loop, all you'll need is a forward iterator. We'll write a simple wrapper for the container that implements the forward iterator requirements, and then write two functions Iterator begin(Container&)
and Iterator end(Container&)
that enable the container to be used in the range-based for loop.
This Iterator
will contain a reference to the container, the size of the container, and the current index within that container:
template<template<typename> class C, typename T>
class indexer : public std::iterator<std::forward_iterator, T>
{
public:
indexer(C<T>& c, std::size_t i, std::size_t end)
: c_(std::addressof(c)), i_(i), end_(end) {}
T& operator*() const {
return c_[i_];
}
indexer<C, T>& operator++() {
++i_;
return *this;
}
indexer<C, T> operator++(int) {
auto&& tmp = *this;
operator++();
return tmp;
}
bool operator==(indexer<C, T> const& other) const {
return i_ == other.i_;
}
bool operator!=(indexer<C, T> const& other) const {
return !(*this == other);
}
private:
C<T>* c_;
std::size_t i_, end_;
};
Inheriting from std::iterator
conveniently declares the appropriate typedefs for use with std::iterator_traits
.
Then, you would define begin
and end
as follows:
template<typename T>
indexer<Container, T> begin(Container<T>& c) {
return indexer<Container, T>(c, 0, c.size());
}
template<typename T>
indexer<Container, T> end(Container<T>& c) {
auto size = c.size();
return indexer<Container, T>(c, size, size);
}
Switch out Container
for whatever the type of container
is in your example, and with that, your desired usage works!
The requirements and behavior of all the various kinds of iterators can be found in the tables of section 24.2.2 of the standard, which are mirrored at cppreference.com here.
A random-access iterator implementation of the above, along with a demo of usage with a simple vector_view
class can be found live on Coliru or ideone.com.