In page 91 of the book Elements of Programming, Stepanov and McJones say that the concept of Iterator requires a successor
function but that is not necessarily regular because
...
i = j
does not imply thatsuccessor(i) = successor(j)
...
(see page online)
I understand the converse successor(i) = successor(j)
does not imply i=j
(for example in two null terminated list) and that successor
function may not be defined for some inputs. But I don't understand how could it be possible that i = j
can lead to successor(i) != successor(j)
.
What case would they be referring to? Perhaps some iterator that does random (as in aleatory) hops? or some iterator that has a hidden state and "jumps" differently than the other iterator after pointing to the same element (and comparing equal in this sense).
They immediately jump to refinements (ForwardIterator) that require a regular successor
function so it is not clear to me.
Initially I thought that an input iterator can have this property. However is still difficult to me to see if this constitutes a counterexample: (within a certain implementation of STL).
#include <iostream>
#include <sstream>
#include <iterator>
#include <numeric>
#include <cassert>
using std::cout; using std::endl;
int main(){
std::istream_iterator<int> it1(std::cin); // wait for one input
std::istream_iterator<int> it2 = it1;
assert(it1 == it2);
cout << "*it1 = " << *it1 << endl;
cout << "*it2 = " << *it2 << endl;
cout << "now sucessor" << endl;
++it1; // wait for one input
++it2; // wait for another input
assert(it1 == it2); // inputs still compare equal !
cout << "*it1 = " << *it1 << endl;
cout << "*it2 = " << *it2 << endl;
assert(it1 == it2); // also here ! and yet they point to different values...
assert(*it1 == *it2); // assert fails!
}
(compiled with GCC 6.1)
To avoid invalidation of references to elements you can use a std::deque if you do not insert or erase in the middle. To avoid invalidation of iterators you can use a std::list.
Iterators are generated by STL container member functions, such as begin() and end(). Some containers return iterators that support only the above operations, while others return iterators that can move forward and backward, be compared with <, and so on.
You can increment your iterator pointer as well as decrement it.
When the container to which an Iterator points changes shape internally, i.e. when elements are moved from one position to another, and the initial iterator still points to the old invalid location, then it is called Iterator invalidation. One should be careful while using iterators in C++.
Consider the type iter
defined as:
struct iter { unsigned value; };
inline bool operator==(iter const& x, iter const& y) {
return x.value == y.value;
}
inline bool operator!=(iter const& x, iter const& y) {
return !(x == y);
}
auto source(iter const& x) {
return x.value;
}
iter successor(iter const&) {
std::random_device engine{};
std::uniform_int_distribution<unsigned> dist{};
return {dist(engine)};
}
IIRC, iter
satisfies the requirements for EoP's Iterator
concept: it is Regular
, source
is a regular function, successor
notably is not regular.
Given two objects i
and j
of type iter
such that i == j
, it is extremely likely that successor(i) != successor(j)
.
An example could be a successor function that consumes a stream of data (as they mention in the book).
When you have read the i-th element, you can theoretically invoke the successor function for it only once. If you try to invoke it twice, the results are different.
Simply imagine that successor(i)
reads the next element from the stream, that is the i-th+1 element. It actually means to consume it and it won't be available anymore. If you call successor(i)
another time, you will get the i-th+2 element from the stream.
Thus, if the inputs are the same (i = j
), you have no guarantees that the outputs are the same (successor(i) = successor(j)
).
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