Iterator is a key concept for C++ STL. When we implement a custom container for C++, we will also have to implement the iterator class for the custom container. In my previous blog post “C++ Iterator”, I discussed about how to implement C++ iterator using nested class inside a certain sequence container class for generic programming. Since C++14, a new const iterator class has been introduced, which is usually returned from the cbegin and cend container methods. In this blog post, I would like to discuss what C++ const iterator is and how to implement const iterator from scratch.
Constant Iterator
C++ iterator was introduced in C++11. Essentially, it is a pointer wrapper and dereferencing the iterator returns the object in the container by reference T&. If we forgot to cast a const to the returned reference, we might modify the objects in the container unintentionally. For example,
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include<iostream> #include<vector> #include<iterator> intmain() { std::vector<int> v = { 3, 1, 4 }; // vi is not a const iterator. // vi cannot even iterate since it is const. constauto vi = std::begin(v); std::cout << *vi << std::endl; *vi = 8; std::cout << *vi << std::endl; }
Since C++14, a new const iterator class was introduced and the user cannot modify the container via const iterator. This time, dereferencing the const iterator returns the object in the container by const reference const T& and we cannot modify the objects in the container accidentally.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include<iostream> #include<vector> #include<iterator> intmain() { std::vector<int> v = { 3, 1, 4 }; // vi is not a const iterator. auto vi = std::cbegin(v); std::cout << *vi << std::endl; // Compile time error. // *vi = 8; std::cout << *vi << std::endl; }
Constant Iterator Implementation for Custom Container
Implementation With Code Duplication
The implementation of iterator and const iterator class are extremely similar, except that dereferencing returns T& for iterator and const T& for const iterator. We can just copy and paste most of the code for the iterator for implementing the const iterator. The drawback is there will be “code duplications”.
// Custom data structure with iterators // For some additional implementations for Vector // /blog/CPP-Const-Overloading/ template <typename T> classDummyVector { public: // Default constructors DummyVector(); // Constructors which take more than zero arguments explicitDummyVector(size_t size); explicitDummyVector(std::initializer_list<T> lst);
// However, the best practice for template classes and functions is to put the // definition and the declaration together. Mainly, there are two reasons // 1. Compiler needs to know all the definition for any templates during compile // time // 2. Namescope is trivial to write and confusing template <typename T> DummyVector<T>::DummyVector() : mSize{0}, mBuffer{nullptr} { }
template <typename T> DummyVector<T>::DummyVector(size_t size) : mSize{size}, mBuffer{new T[size]} { for (int i = 0; i < mSize; i++) { mBuffer[i] = 0; } }
// Prefix overloading // If T::X is a dependent typename, then we need to prefix it by typename to // tell the compiler to parse it in a certain way. template <typename T> typename DummyVector<T>::Iterator& DummyVector<T>::Iterator::operator++() { this->mPtr++; return *this; } // Prefix overloading template <typename T> typename DummyVector<T>::Iterator& DummyVector<T>::Iterator::operator--() { this->mPtr--; return *this; }
// Prefix overloading // If T::X is a dependent typename, then we need to prefix it by typename to // tell the compiler to parse it in a certain way. template <typename T> typename DummyVector<T>::ConstIterator& DummyVector<T>::ConstIterator::operator++() { this->mPtr++; return *this; } // Prefix overloading template <typename T> typename DummyVector<T>::ConstIterator& DummyVector<T>::ConstIterator::operator--() { this->mPtr--; return *this; }
// Template function for iterators template <typename Iter> Iter maximum(Iter first, Iter last) { Iter max = first; for (Iter iter = first; iter != last; iter++) { if (*max < *iter) { max = iter; } } return max; }
// Custom data structure with iterators // For some additional implementations for Vector // /blog/CPP-Const-Overloading/ template <typename T> classDummyVector { public: // Default constructors DummyVector(); // Constructors which take more than zero arguments explicitDummyVector(size_t size); explicitDummyVector(std::initializer_list<T> lst);
// However, the best practice for template classes and functions is to put the // definition and the declaration together. Mainly, there are two reasons // 1. Compiler needs to know all the definition for any templates during compile // time // 2. Namescope is trivial to write and confusing template <typename T> DummyVector<T>::DummyVector() : mSize{0}, mBuffer{nullptr} { }
template <typename T> DummyVector<T>::DummyVector(size_t size) : mSize{size}, mBuffer{new T[size]} { for (int i = 0; i < mSize; i++) { mBuffer[i] = 0; } }
// Prefix overloading // If T::X is a dependent typename, then we need to prefix it by typename to // tell the compiler to parse it in a certain way. template <typename T> template <bool Const> typename DummyVector<T>::template Iterator<Const>& DummyVector<T>::Iterator<Const>::operator++() { this->mPtr++; return *this; } // Prefix overloading template <typename T> template <bool Const> typename DummyVector<T>::template Iterator<Const>& DummyVector<T>::Iterator<Const>::operator--() { this->mPtr--; return *this; }
// Template function for iterators template <typename Iter> Iter maximum(Iter first, Iter last) { Iter max = first; for (Iter iter = first; iter != last; iter++) { if (*max < *iter) { max = iter; } } return max; }