How often have you written a line of code that looks something like this?
if (3 <= var && 14 >= var)
There might (read “should”) be named constant variables instead of the magic numbers there, but in essence it’s a very common piece of code for a very common type of test – is this value within pre-defined, constant bounds?
Some years ago, I was working on a project that had lots of tests like that, and I came across a surprisingly large number of errors one can commit with this simple code. For instance:
if (minTempUK <= var && maxTempUS >= var)
if (minTempUK < var && maxTempUK >= var)
if (maxTempUK <= var && minTempUK >= var)
if (minTempUK <= var <= maxTempUK)
if (minTempUK <= var & maxTempUK >= var)
All of these are legal C++, and only the last two or three might generate compiler warnings. The last would still work properly, but is a bit iffy. If it isn’t a typo, someone needs to read up on operators. In most cases, these errors were typos (except the fourth, which was written by someone more used to other languages), but since they compiled, and sort of worked, they only showed up as bugs every now and then, at the edge cases. And because the code looks sort of okay, it was hard to spot the typos right away.
The thing is, there’s nothing wrong with writing this line of code...
if (minTempUK <= var && maxTempUK >= var)
... as long as it’s written correctly. It’s not an intrinsically unsafe or “bad” construct.
But after having fixed a fair few bugs caused by simple mistakes like the ones above, I sat down and hacked out a little helper, which looked more or less like this:
template <typename T, T lower_, T upper_>
struct bounds
{
typedef typename T type;
static bool in_bounds(const T& value)
{
return (lower_ <= value) && (value <= upper_);
}
static inline type lower_bound()
{
return lower_;
}
static inline type upper_bound()
{
return upper_;
}
};
typedef bounds<int, 3, 14> MyBounds;
...
if (MyBounds::in_bounds(var))
...
for (MyBounds::type i = MyBounds::lower_bound();
MyBounds::in_bounds(i); ++i)
Very simple and basic, but quite useful, both because it keeps the lower and upper bounds together and treats them as a pair, and because it removes the risk of typos in the comparison. I added a static
assert to validate the template parameters at compile time, and left it at that.
The mathematically astute will recognize this bounds
type as a proper, bounded and closed interval, or “[lower_, upper_]
“. In other words, it’s inclusive of the boundary values, and the comparison done is always “<=
”. In code, however, we are more used to see left-closed, right-open intervals, or “[lower_, upper_)
", where the left comparison is "<=
" and the right is "<
". What's up with that?
Well, there are very good reasons for the canonical C/C++ usage of half-open intervals, but this class is designed to represent boundaries, to see if a given value falls within a given interval. For this purpose, a half-open interval would be confusing - a variable could have the minimum value defined, but not the maximum.
But okay, it's not complicated to accommodate half-open intervals, nor is it necessarily slowing the code down. We just have to provide a way to indicate the desired comparators. It does, however, possibly invalidate that static
sanity check I mentioned.
namespace less_than_comparison
{
template <typename T>
struct open
{
static inline bool less(const T& lhs, const T& rhs)
{ return (lhs < rhs); }
};
template <typename T>
struct closed
{
static inline bool less(const T& lhs, const T& rhs)
{ return (lhs <= rhs); }
};
}
template <typename T, T lower_, T upper_ , typename L = less_than_comparison::closed<T>, typename U = less_than_comparison::closed<T> >struct bounds
{
private:
template <bool b> struct bounds_validation{};
template <> struct bounds_validation<true>
{ static void lower_larger_than_upper() {}; };
public:
typedef T type;
static bool in_bounds(const type& val)
{
bounds_validation<lower_ <= upper_>::lower_larger_than_upper();
return L::less(lower_, val) && U::less(val, upper_);
}
static inline type lower_bound()
{
bounds_validation<lower_ <= upper_>::lower_larger_than_upper();
return lower_;
}
static inline type upper_bound()
{
bounds_validation<lower_ <= upper_>::lower_larger_than_upper();
return upper_;
}
};
The validation problem is that [1,1]
is valid and equal to {1}, (1,1)
is valid but empty, but [1,1)
and (1,1]
are both nonsensical. Mathematically, they would be regarded as empty, just like (1,1)
is, but I think that should one of them appear in my codebase, it's likely an error. Unfortunately, there is (yet) no way to check for those cases.
I say yet, because in C++0x we'll get decltype
, which will let us check whether the comparators are the same (open, closed) or different (half-open), like this:
bounds_validation<
((lower_ <= upper_) && (decltype(L) == decltype(U))) ||
((lower_ < upper_) && (decltype(L) != decltype(U)))
>::lower_larger_than_upper();
But until your compiler(s) supports decltype
, you'll have to make do with the simpler check.
Apart from that little niggle, the class is complete here, and useful for all your min-max boundaries. (Provided, of course, that they are integers. I'll come back to this subject.)
Again, if you find it useful, or have suggestions for improvements, please let me know.
Tagged:
bounds,
C++,
template
CodeProject