Neither "half-open intervals" nor "circular buffers" are valid reasons to justify why one option is distinctly superior. The following illustrates the existence of constructs with identical verbosity and complexity for either form of array-indexing.
[1] Half-open intervals
A common argument in favor of zero-based indexing is claiming
0 <= idx < n
being more elegant than either of these
[0, n) is simply a form of half-open intervals, specifically, a right-open interval. It's equivalent counter part is a left-open interval (0, n].
For one-based indexing, the equivalent expression would be
0 < idx <= n
Thus allowing the idiomatic zero-based for() loop construct to be directly repurposed for one-based indexing with a very minor tweak
for (idx = 0; idx < n; idx++) {..} # zero-based
for (idx = 0; idx++ < n; ) {..} # one-based
The risk of "off-by-one" errors are equivalent in both forms, because the initial condition, relational comparison operator, the high side threshold/cap, as well as the value of idx being compared against, are identical in both forms.
The sole difference being when post-increment action is performed - before each loop cycle, or afterwards. If anything, one-based variant has a slight reduction in verbosity.
[2] Circular Buffers
Another common argument in favor of zero-based indexing is claiming
idx = (idx + 1) % n
being more elegant than requiring the extra + 1 every time
idx = (idx + 1) % n + 1
in a circular or cyclical data structure. Right off the bat, one observes the actual equivalent expression for one-based indexing should be
idx = (idx) % n + 1
idx = idx % n + 1 (once you prune out the now-superfluous pair of ( ))
Both zero- and one-based variants perform the exact same set of operations, in a different order. But are modulos the only way to do a circular buffer ?
In the unsigned-only sense, what modulo % actually does is same as multiplying a boolean outcome
idx *= ++idx < n
because the underlying objective is simply - reset the index (idx) at any point of your choosing such that its long-run cyclicality has a period of n. Zero-based indexing requires resetting to zero, so the appropriate operators are either modulo % or multiplication *.
So what's the equivalent resetting mechanism/operator for one-based indexing ? Exponentation -
idx ^= idx++ < n
...because a FALSE boolean outcome means taking the zero-th power of the base, thus resetting the index back to 1. The key difference being that zero-based index resets upon idx % n == 0, one-based resets upon idx % n == 1 but only when the input size exceeds the size of the circular buffer.
In fact, any one of these 5 expressions achieve the same circular buffer for one-based indexing :
idx = idx % n + 1
idx ^= idx++ < n
idx = ++idx^(idx <= n)
idx = (1+idx)^(idx < n)
idx += (1 - n)^(idx == n)
1=1 2=2 3=3 4=1 5=2 6=3 7=1 8=2 9=3
1=1 2=2 3=3 4=1 5=2 6=3 7=1 8=2 9=3
1=1 2=2 3=3 4=1 5=2 6=3 7=1 8=2 9=3
1=1 2=2 3=3 4=1 5=2 6=3 7=1 8=2 9=3
1=1 2=2 3=3 4=1 5=2 6=3 7=1 8=2 9=3
[ps:] Zero-based indexing also means there isn't a single unsigned value for representing a never-possibly-valid and/or non-existent index value, unless one either
arbitrarily carving out and designating UINT_MAX (of any integer width) for such a value (and a comprehensive framework to enforce this constraint),
opts for singular constructs such as None / nil / null, which likely entails returning a list or tuple, multiple values, or an encapsulated data type, or
catch exceptions being thrown, leading to functional "impurity" in the strict functional programming sense.
Instead of being forced to choose among 3 simplicity-compromising verbosity-amplifying options, with one-based indexing, the choice for such a special value is self-apparent :
0
-
Sensible mappings for boolean interpretation of these index values come free of charge - all valid indices are boolean True, and the special value for never-possibly-valid / non-existent index is boolean False
In other words, instead of having another function for index_exists(), just directly type recast int -> bool the unsigned return value of an index() function.
1.