Bidirectional Iterators in C++

Salient Features
- Usability: Since, forward iterators can be used in multi-pass algorithms, i.e., algorithm which involves processing the container several times in various passes, therefore bidirectional iterators can also be used in multi-pass algorithms. .
- Equality / Inequality Comparison: A Bidirectional iterator can be compared for equality with another iterator. Since, iterators point to some location, so the two iterators will be equal only when they point to the same position, otherwise not.
So, the following two expressions are valid if A and B are Bidirectional iterators:
A == B // Checking for equality A != B // Checking for inequality
- Dereferencing: Because an input iterator can be dereferenced, using operator * and -> as an rvalue and an output iterator can be dereferenced as an lvalue, so forward iterators being the combination of both can be used for both the purposes, and similarly, bidirectional operators can also serve both the purposes.
CPP // C++ program to demonstrate bidirectional iterator #include<iostream> #include<list> using namespace std; int main() { list<int>v1 = {1, 2, 3, 4, 5}; // Declaring an iterator list<int>::iterator i1; for (i1=v1.begin();i1!=v1.end();++i1) { // Assigning values to locations pointed by iterator *i1 = 1; } for (i1=v1.begin();i1!=v1.end();++i1) { // Accessing values at locations pointed by iterator cout << (*i1) << " "; } return 0; }
1 1 1 1 1
So, as we can see here we can both access as well as assign value to the iterator, therefore the iterator is a bidirectional iterator. - Incrementable: A Bidirectional iterator can be incremented, so that it refers to the next element in sequence, using operator ++().
So, the following two expressions are valid if A is a bidirectional iterator:
A++ // Using post increment operator ++A // Using pre increment operator
- Decrementable: This is the feature which differentiates a Bidirectional iterator from a forward iterator. Just like we can use operator ++() with bidirectional iterators for incrementing them, we can also decrement them.
That is why, its name is bidirectional, which shows that it can move in both directions.
CPP // C++ program to demonstrate bidirectional iterator #include<iostream> #include<list> using namespace std; int main() { list<int>v1 = {1, 2, 3, 4, 5}; // Declaring an iterator list<int>::iterator i1; // Accessing the elements from end using decrement // operator for (i1=v1.end();i1!=v1.begin();--i1) { if (i1 != v1.end()) { cout << (*i1) << " "; } } cout << (*i1); return 0; }
5 4 3 2 1
Since, we are starting from the end of the list and then moving towards the beginning by decrementing the pointer, which shows that decrement operator can be used with such iterators. Here, we have run the loop till the iterator becomes equal to the begin(), that is why the first value is not printed inside the loop and we have printed it separately. - Swappable: The value pointed to by these iterators can be exchanged or swapped.
Practical implementation
After understanding its features, it is very important to learn about its practical implementation as well. As told earlier, Bidirectional iterators can be used for all the purposes that forward iterator can be used along within those situations where iterator needs to be decremented. The following two STL algorithms can show this fact:- std::reverse_copy: As the name suggests, this algorithm is used to copy a range into another range, but in reverse order. Now, as far as accessing elements and assigning elements are concerned, forward iterators are fine, but as soon as we have to decrement the iterator, then we cannot use these forward iterators for this purpose, and that's where bidirectional iterators come for our rescue.
CPP // Definition of std::reverse_copy() template OutputIterator reverse_copy(BidirectionalIterator first, BidirectionalIterator last, OutputIterator result) { while (first != last) *result++ = *--last; return result; }
- std::random_shuffle: As we know this algorithm is used to randomly shuffle all the elements present in a container. So, let us look at its internal working (Don't go into detail just look where bidirectional iterators can be used and where they cannot be):
CPP // Definition of std::random_shuffle() template void random_shuffle(RandomAccessIterator first, RandomAccessIterator last, RandomNumberGenerator& gen) { iterator_traits::difference_type i, n; n = (last - first); for (i=n-1; i>0; --i) { swap (first[i],first[gen(i+1)]); } }
Limitations
After studying the salient features, one must also know its deficiencies as well although there are not as many as there are in input or output iterators as it is higher in the hierarchy.- Relational Operators: Although, Bidirectional iterators can be used with equality operator (==), but it can not be used with other relational operators like , =.
If A and B are Bidirectional iterators, then A == B // Allowed A <= B // Not Allowed
- Arithmetic Operators: Similar to relational operators, they also can't be used with arithmetic operators like +, - and so on. This means that bidirectional iterators can move in both the direction, but sequentially.
If A and B are Bidirectional iterators, then A + 1 // Not allowed B - 2 // Not allowed
CPP // C++ program to demonstrate bidirectional iterator #include<iostream> #include<list> using namespace std; int main() { list<int>v1 = {1, 2, 3, 4, 5}; // Declaring first iterator list<int>::iterator i1; // Declaring second iterator list<int>::iterator i2; // i1 points to the beginning of the list i1 = v1.begin(); // i2 points to the end of the list i2 = v1.end(); // Applying relational operator to them if ( i1 > i2) { cout << "Yes"; } // This will throw an error because relational // operators cannot be applied to bidirectional iterators // Applying arithmetic operator to them int count = i1 - i2; // This will also throw an error because arithmetic // operators cannot be applied to bidirectional iterators return 0; }
Error, because of use of arithmetic and relational operators with bidirectional iterators
- Use of offset dereference operator ([ ]): Bidirectional iterators doesnot support offset dereference operator ([ ]), which is used for random-access.
If A is a Bidirectional iterator, then A[3] // Not allowed