To create an Iterator class we need to override __next__() function inside our class i.e. __next__() function should be implemented in such a way that every time we call the function it should return the next element of the associated Iterable class. If there are no more elements then it should raise StopIteration.
In general: If you pass a non- const reference, the caller doesn't know if the iterator is being modified. You could pass a const reference, but usually iterators are small enough that it gives no advantage over passing by value.
std::iterator
with random_access_iterator_tag
.These base classes define all type definitions required by STL and do other work.To avoid code duplication iterator class should be a template class and be parametrized by "value type", "pointer type", "reference type" or all of them (depends on implementation). For example:
// iterator class is parametrized by pointer type
template <typename PointerType> class MyIterator {
// iterator class definition goes here
};
typedef MyIterator<int*> iterator_type;
typedef MyIterator<const int*> const_iterator_type;
Notice iterator_type
and const_iterator_type
type definitions: they are types for your non-const and const iterators.
See Also: standard library reference
EDIT: std::iterator
is deprecated since C++17. See a relating discussion here.
I'm going to show you how you can easily define iterators for your custom containers, but just in case I have created a c++11 library that allows you to easily create custom iterators with custom behavior for any type of container, contiguous or non-contiguous.
You can find it on Github
Here are the simple steps to creating and using custom iterators:
typedef blRawIterator< Type > iterator;
typedef blRawIterator< const Type > const_iterator;
iterator begin(){return iterator(&m_data[0]);};
const_iterator cbegin()const{return const_iterator(&m_data[0]);};
Finally, onto defining our custom iterator classes:
NOTE: When defining custom iterators, we derive from the standard iterator categories to let STL algorithms know the type of iterator we've made.
In this example, I define a random access iterator and a reverse random access iterator:
//-------------------------------------------------------------------
// Raw iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawIterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = blDataType;
using difference_type = std::ptrdiff_t;
using pointer = blDataType*;
using reference = blDataType&;
public:
blRawIterator(blDataType* ptr = nullptr){m_ptr = ptr;}
blRawIterator(const blRawIterator<blDataType>& rawIterator) = default;
~blRawIterator(){}
blRawIterator<blDataType>& operator=(const blRawIterator<blDataType>& rawIterator) = default;
blRawIterator<blDataType>& operator=(blDataType* ptr){m_ptr = ptr;return (*this);}
operator bool()const
{
if(m_ptr)
return true;
else
return false;
}
bool operator==(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr == rawIterator.getConstPtr());}
bool operator!=(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr != rawIterator.getConstPtr());}
blRawIterator<blDataType>& operator+=(const difference_type& movement){m_ptr += movement;return (*this);}
blRawIterator<blDataType>& operator-=(const difference_type& movement){m_ptr -= movement;return (*this);}
blRawIterator<blDataType>& operator++(){++m_ptr;return (*this);}
blRawIterator<blDataType>& operator--(){--m_ptr;return (*this);}
blRawIterator<blDataType> operator++(int){auto temp(*this);++m_ptr;return temp;}
blRawIterator<blDataType> operator--(int){auto temp(*this);--m_ptr;return temp;}
blRawIterator<blDataType> operator+(const difference_type& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
blRawIterator<blDataType> operator-(const difference_type& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
difference_type operator-(const blRawIterator<blDataType>& rawIterator){return std::distance(rawIterator.getPtr(),this->getPtr());}
blDataType& operator*(){return *m_ptr;}
const blDataType& operator*()const{return *m_ptr;}
blDataType* operator->(){return m_ptr;}
blDataType* getPtr()const{return m_ptr;}
const blDataType* getConstPtr()const{return m_ptr;}
protected:
blDataType* m_ptr;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Raw reverse iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawReverseIterator : public blRawIterator<blDataType>
{
public:
blRawReverseIterator(blDataType* ptr = nullptr):blRawIterator<blDataType>(ptr){}
blRawReverseIterator(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();}
blRawReverseIterator(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
~blRawReverseIterator(){}
blRawReverseIterator<blDataType>& operator=(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
blRawReverseIterator<blDataType>& operator=(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();return (*this);}
blRawReverseIterator<blDataType>& operator=(blDataType* ptr){this->setPtr(ptr);return (*this);}
blRawReverseIterator<blDataType>& operator+=(const difference_type& movement){this->m_ptr -= movement;return (*this);}
blRawReverseIterator<blDataType>& operator-=(const difference_type& movement){this->m_ptr += movement;return (*this);}
blRawReverseIterator<blDataType>& operator++(){--this->m_ptr;return (*this);}
blRawReverseIterator<blDataType>& operator--(){++this->m_ptr;return (*this);}
blRawReverseIterator<blDataType> operator++(int){auto temp(*this);--this->m_ptr;return temp;}
blRawReverseIterator<blDataType> operator--(int){auto temp(*this);++this->m_ptr;return temp;}
blRawReverseIterator<blDataType> operator+(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr-=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
blRawReverseIterator<blDataType> operator-(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr+=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
difference_type operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}
blRawIterator<blDataType> base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
};
//-------------------------------------------------------------------
Now somewhere in your custom container class:
template<typename blDataType>
class blCustomContainer
{
public: // The typedefs
typedef blRawIterator<blDataType> iterator;
typedef blRawIterator<const blDataType> const_iterator;
typedef blRawReverseIterator<blDataType> reverse_iterator;
typedef blRawReverseIterator<const blDataType> const_reverse_iterator;
.
.
.
public: // The begin/end functions
iterator begin(){return iterator(&m_data[0]);}
iterator end(){return iterator(&m_data[m_size]);}
const_iterator cbegin(){return const_iterator(&m_data[0]);}
const_iterator cend(){return const_iterator(&m_data[m_size]);}
reverse_iterator rbegin(){return reverse_iterator(&m_data[m_size - 1]);}
reverse_iterator rend(){return reverse_iterator(&m_data[-1]);}
const_reverse_iterator crbegin(){return const_reverse_iterator(&m_data[m_size - 1]);}
const_reverse_iterator crend(){return const_reverse_iterator(&m_data[-1]);}
.
.
.
// This is the pointer to the
// beginning of the data
// This allows the container
// to either "view" data owned
// by other containers or to
// own its own data
// You would implement a "create"
// method for owning the data
// and a "wrap" method for viewing
// data owned by other containers
blDataType* m_data;
};
They often forget that iterator
must convert to const_iterator
but not the other way around. Here is a way to do that:
template<class T, class Tag = void>
class IntrusiveSlistIterator
: public std::iterator<std::forward_iterator_tag, T>
{
typedef SlistNode<Tag> Node;
Node* node_;
public:
IntrusiveSlistIterator(Node* node);
T& operator*() const;
T* operator->() const;
IntrusiveSlistIterator& operator++();
IntrusiveSlistIterator operator++(int);
friend bool operator==(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
friend bool operator!=(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
// one way conversion: iterator -> const_iterator
operator IntrusiveSlistIterator<T const, Tag>() const;
};
In the above notice how IntrusiveSlistIterator<T>
converts to IntrusiveSlistIterator<T const>
. If T
is already const
this conversion never gets used.
Boost has something to help: the Boost.Iterator library.
More precisely this page: boost::iterator_adaptor.
What's very interesting is the Tutorial Example which shows a complete implementation, from scratch, for a custom type.
template <class Value> class node_iter : public boost::iterator_adaptor< node_iter<Value> // Derived , Value* // Base , boost::use_default // Value , boost::forward_traversal_tag // CategoryOrTraversal > { private: struct enabler {}; // a private type avoids misuse public: node_iter() : node_iter::iterator_adaptor_(0) {} explicit node_iter(Value* p) : node_iter::iterator_adaptor_(p) {} // iterator convertible to const_iterator, not vice-versa template <class OtherValue> node_iter( node_iter<OtherValue> const& other , typename boost::enable_if< boost::is_convertible<OtherValue*,Value*> , enabler >::type = enabler() ) : node_iter::iterator_adaptor_(other.base()) {} private: friend class boost::iterator_core_access; void increment() { this->base_reference() = this->base()->next(); } };
The main point, as has been cited already, is to use a single template implementation and typedef
it.
I don't know if Boost has anything that would help.
My preferred pattern is simple: take a template argument which is equal to value_type
, either const qualified or not. If necessary, also a node type. Then, well, everything kind of falls into place.
Just remember to parameterize (template-ize) everything that needs to be, including the copy constructor and operator==
. For the most part, the semantics of const
will create correct behavior.
template< class ValueType, class NodeType >
struct my_iterator
: std::iterator< std::bidirectional_iterator_tag, T > {
ValueType &operator*() { return cur->payload; }
template< class VT2, class NT2 >
friend bool operator==
( my_iterator const &lhs, my_iterator< VT2, NT2 > const &rhs );
// etc.
private:
NodeType *cur;
friend class my_container;
my_iterator( NodeType * ); // private constructor for begin, end
};
typedef my_iterator< T, my_node< T > > iterator;
typedef my_iterator< T const, my_node< T > const > const_iterator;
There are plenty of good answers but I created a template header I use that is quite concise and easy to use.
To add an iterator to your class it is only necessary to write a small class to represent the state of the iterator with 7 small functions, of which 2 are optional:
#include <iostream>
#include <vector>
#include "iterator_tpl.h"
struct myClass {
std::vector<float> vec;
// Add some sane typedefs for STL compliance:
STL_TYPEDEFS(float);
struct it_state {
int pos;
inline void begin(const myClass* ref) { pos = 0; }
inline void next(const myClass* ref) { ++pos; }
inline void end(const myClass* ref) { pos = ref->vec.size(); }
inline float& get(myClass* ref) { return ref->vec[pos]; }
inline bool cmp(const it_state& s) const { return pos != s.pos; }
// Optional to allow operator--() and reverse iterators:
inline void prev(const myClass* ref) { --pos; }
// Optional to allow `const_iterator`:
inline const float& get(const myClass* ref) const { return ref->vec[pos]; }
};
// Declare typedef ... iterator;, begin() and end() functions:
SETUP_ITERATORS(myClass, float&, it_state);
// Declare typedef ... reverse_iterator;, rbegin() and rend() functions:
SETUP_REVERSE_ITERATORS(myClass, float&, it_state);
};
Then you can use it as you would expect from an STL iterator:
int main() {
myClass c1;
c1.vec.push_back(1.0);
c1.vec.push_back(2.0);
c1.vec.push_back(3.0);
std::cout << "iterator:" << std::endl;
for (float& val : c1) {
std::cout << val << " "; // 1.0 2.0 3.0
}
std::cout << "reverse iterator:" << std::endl;
for (auto it = c1.rbegin(); it != c1.rend(); ++it) {
std::cout << *it << " "; // 3.0 2.0 1.0
}
}
I hope it helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With