How to correctly implement custom iterators and const_iterators?
I have a custom container class for which I'd like to write the iterator
and const_iterator
classes.
I never did this before and I failed to find an appropriate how-to. What are the guidelines regarding iterator creation, and what should I be aware of ?
I'd also like to avoid code duplication (I feel that const_iterator
and iterator
share many things; should one subclass the other ?).
Foot note: I'm pretty sure Boost has something to ease this but I can't use it here, for many stupid reasons.
- Choose type of iterator which fits your container: input, output, forward etc.
- Use base iterator classes from standard library. For example,
std::iterator
withrandom_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
andconst_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:
- Create your "custom iterator" class.
- Define typedefs in your "custom container" class.
- e.g.
typedef blRawIterator< Type > iterator;
- e.g.
typedef blRawIterator< const Type > const_iterator;
- e.g.
- Define "begin" and "end" functions
- e.g.
iterator begin(){return iterator(&m_data[0]);};
- e.g.
const_iterator cbegin()const{return const_iterator(&m_data[0]);};
- e.g.
- We're Done!!!
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.