The central motivation for introducing a sentinel is that there are a lot of iterator operations which are supported but usually never needed for the end-iterator end()
. For example, there is hardly any point in dereferencing it via *end()
, in incrementing it via ++end()
, and so on (*).
In contrast, the main usage of end()
is merely to compare it with an iterator it
in order to signalize whether it
is at the end of the thing it just iterates. And, as usual in programming, different requirements and different application suggest a new type.
The range-v3 library turns this observation into an assumption (that is implemented through a concept): it introduces a new type for end()
and only requires that it is equality-comparable with the corresponding iterator -- but does not require the usual iterator operations). This new type of end()
is called a sentinel.
The main advantage here is the gained abstraction and better separation of concerns, based on which the compiler is possibly able to perform a better optimization. In code, the basic idea is this (this is just for explanation and has nothing to do with the range-v3 library):
struct my_iterator; //some iterator
struct my_sentinel
{
bool is_at_end(my_iterator it) const
{
//here implement the logic when the iterator is at the end
}
};
auto operator==(my_iterator it, my_sentinel s) //also for (my_sentinel s, my_iterator it)
{
return s.is_at_end(it);
}
See the abstraction? Now, you can implement any check you want in the is_at_end
function, e.g.:
- stop never (get an infinite range)
- stop after
N
increments (to get a counted range)
- stop when a
\0
is encountered, i.e. *it = '\0'
(for looping over C-strings)
- stop when it's 12'o clock (for having lunch), and so on.
Moreover, regarding performance, one is able to make use of compile time-information in the check (e.g., think of the N
above as a compile-time parameter). In this case, the compiler might possibly be able to better optimize the code.
(*) Note that this does not mean there is in general no use for this kind of operations. For example, --end()
can be useful in some places, see e.g. this question. However, it is seemingly possible to implement the standard library without these -- this is what the range-v3 library has done.