Despite my comment, Sander De Dycker has the right idea. I have another way to think about it.
All containers that support iterators have a logical ordering. For vector
the ordering is based on how the inserts were done - the index/subscript ordering. For map
and set
it's based on the key ordering. For multimap
and multiset
it's a bit of both. For unordered_map
etc the claim is very tenuous, but I can still argue about hash algorithms and collision handling.
In a logical ordering, you can refer to ordered elements, but sometimes it makes sense to refer to the boundaries between each element. Logically (and in some cases even for the implementation) this works out fairly conveniently...
| | | | | | | | |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| |0| | |1| | |2| | |3| | |4| | |5| | |6| | |7| |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| | | | | | | | |
0 1 2 3 4 5 6 7 8
You decide where the zero "bound" goes independently of where the zero item goes, but you always get a simple addition/subtraction relationship. If the least bound is numbered the same as the least element, the last bound is numbered one more than the last element. Hence end
as one past the final element.
In a binary tree implementation, each node can be considered to have two bounds - one either side of the element. In this scheme, every bound except begin
and end
occurs twice. You can represent bound 1 using the RHS of element 0 or the LHS or element 1. So in principle you can use a node pointer and a flag. Rather than have two representations for most bounds, though, you'll probably choose the most convenient one where possible - the one where you're not just referring to the right bound but also referring to the element you want to see when you dereference. That means the flag will only be set when referring to end
, in which case you shouldn't support dereference anyway.
IOW following through this logic tells you that you don't really need to follow through this logic, though I think it's still a useful mental model. All you really need is an identifiable representation for end
. Perhaps it's useful for that representation to include a pointer to the final pointer (as a starting point for e.g. decrementing that iterator). Perhaps there are situations where it's convenient to have pseudo-iterators internally that recognize the two equivalent bounds as distinct.
Similar but slightly different models and choices arise thinking about e.g. multiway trees where each node contains an array of elements.
Basically I think it's useful to mentally recognise bound positions as distinct but related to item positions, but that mental model shouldn't constraint your implementation choices - it may inspire alternatives but it's just a mental model.