Why does the Standard define end()
as one past the end, instead of at the actual end?
The best argument easily is the one made by Dijkstra himself:
You want the size of the range to be a simple difference end − begin;
including the lower bound is more "natural" when sequences degenerate to empty ones, and also because the alternative (excluding the lower bound) would require the existence of a "one-before-the-beginning" sentinel value.
You still need to justify why you start counting at zero rather than one, but that wasn't part of your question.
The wisdom behind the [begin, end) convention pays off time and again when you have any sort of algorithm that deals with multiple nested or iterated calls to range-based constructions, which chain naturally. By contrast, using a doubly-closed range would incur off-by-ones and extremely unpleasant and noisy code. For example, consider a partition [n0, n1)[n1, n2)[n2,n3). Another example is the standard iteration loop for (it = begin; it != end; ++it)
, which runs end - begin
times. The corresponding code would be much less readable if both ends were inclusive – and imagine how you'd handle empty ranges.
Finally, we can also make a nice argument why counting should start at zero: With the half-open convention for ranges that we just established, if you are given a range of N elements (say to enumerate the members of an array), then 0 is the natural "beginning" so that you can write the range as [0, N), without any awkward offsets or corrections.
In a nutshell: the fact that we don't see the number 1
everywhere in range-based algorithms is a direct consequence of, and motivation for, the [begin, end) convention.