Asymmetric Bounds, Part 5: Rules of Thumb
This series has concentrated more on why than on how. This installment offers concrete advice about how to make programming easier when you use asymmetric bounds.
- Think of every bound as the beginning of a range. When you want the end of a range, use the representation for the beginning of what immediately follows the original range. In other words, instead of thinking of the integers from 1 through 5, think about one range of integers that starts at 1 and another that starts at 6, which is the value immediately after the last value of the preceding range. Thinking in this way leads to representing such a range as [1, 6) rather than as [1, 5], which is useful because…
- The bounds of an empty asymmetric range are equal to each other. In other words, the range [42, 42) is empty because 42 is equal to 42.
- More generally, the number of elements in such a range is equal to the difference between the bounds. It is easier to see that [1, 6) has 6-1 elements than it is to figure out how many elements are in [1, 5].
- Two ranges are consecutive if the end of the first range is equal to the beginning of the second. In other words, the ranges
[a, b)and[b, c)are contiguous becausebis equal tob. Moreover, combining them yields[a, c). The arithmetic here is much easier to follow than it would be for asymmetric ranges. - Use (in)equality comparisons to step through asymmetric ranges. Because the range ends just before the upper bound, there is no need to use order relations. The usual form for stepping through a range
[begin, end)ispos = begin; while (pos != end) { process the element at pos; ++pos; } - To step backward through a range, decrement at the beginning. Asymmetric ranges are asymmetric, which means that the beginning and end of the range must be treated differently. This property is one of the few ways in which symmetric ranges are easier to use than asymmetric ranges. The point is that the end of the range does not denote an element, which means that beginning a loop by accessing the element at the end immediately doesn't work: There is no element there to access. Instead, step backward through the range
[begin, end)this way:pos = end; while (pos != begin) { --pos; process the element at pos; }
Again, this technique avoids having to use relational operators. Moreover, it avoids ever having pos refer to the nonexistent position before the beginning of the range, because unlike the case with the off-the-end value, there is no reason to believe that an off-the-beginning value is even possible.
These guidelines make many programming problems simpler. Even though many programmers find asymmetric bounds peculiar at first, one quickly finds that programming problems that involve ranges are usually much easier to follow with asymmetric ranges than they are with symmetric ones.

