Continuing on the train of thought started in `bounds`

class I presented a few days ago in Bounds, and staying within them.

As so often happens, just having `bounds`

available made me think of what variants of it could be useful. For instance, it would be handy to have it work for floating point or non-POD types, which isn’t possible as it is written. Since the `bounds`

class uses ‘non-type template parameters‘ for its limits, only integer types and enums are accepted.[1]

Even disregarding this restriction, I found that I had use for a dynamic `range`

class, as opposed to the static `bounds`

which has its boundaries set at compile time. Just a simple one, and like `std::pair`

only having two values, but with both of the same type, and with them guaranteed to be ordered.

The last part there would make it a bit more complex than the simple `std::pair`

struct, as I'd need to validate the values given in order to ensure that the minimum was lower than or equal to the maximum, but still, a simple enough little class.

template <typename T,
typename L = less_than_comparison::closed<T>,
typename U = less_than_comparison::closed<T> >
class range
{
T minimum_, maximum_;
protected:
virtual void throw_if_invalid(const T& minimum,
const T& maximum)
{
if (maximum < minimum)
throw std::invalid_argument("Minimum > maximum");
}
public:
typedef typename T type;
range()
: minimum_(T()),maximum_(T())
{}
range(T min, T max)
: minimum_(min),maximum_(max)
{
throw_if_invalid(minimum_, maximum_);
}
T get_minimum() const
{
return minimum_;
}
T get_maximum() const
{
return maximum_;
}
void set_minimum(T min)
{
throw_if_invalid(min, maximum_);
minimum_ = min;
}
void set_maximum(T max)
{
throw_if_invalid(minimum_, max);
maximum_ = max;
}
bool operator==(const range& other) const
{
return (minimum_ == other.minimum_) &&
(maximum_ == other.maximum_);
}
bool operator!=(const range& other) const
{
return !operator==(other);
}
int width() const
{
return maximum_ - minimum_;
}
bool in_range(T val) const
{
return L::less(minimum_, val) && U::less(val, maximum_);
}
bool in_range(const range& other) const
{
return L::less(minimum_, other.minimum_) &&
U::less(other.maximum_, maximum_);
}
bool intersects(const range& other) const
{
return
(in_range(other.minimum_) || other.in_range(minimum_)) &&
(in_range(other.maximum_) || other.in_range(maximum_));
}
range make_union(const range& other) const
{
if (!intersects(other))
throw std::invalid_argument("No union of ranges");
return range(std::min(minimum_, other.minimum_),
std::max(maximum_, other.maximum_));
}
range make_intersection(const range& other) const
{
if (!intersects(other))
throw std::invalid_argument("No intersection of ranges");
return range(std::max(minimum_, other.minimum_),
std::min(maximum_, other.maximum_));
}
};
template <typename T>
range<T> operator&(const range<T>& lhs,
const range<T>& rhs)
{
return lhs.make_intersection(rhs);
}
template <typename T>
range<T> operator|(const range<T>& lhs,
const range<T>& rhs)
{
return lhs.make_union(rhs);
}

This uses the same policy-based design with upper and lower comparators as the `bounds`

class, so that you can have an open, closed, or half-open (either directions) range.

Note that despite the inclusion of union and intersection functions and operators, this is not a class intended for interval arithmetic. If you have such needs, you're much better off with the boost::interval class.

There is one virtual function in the `range`

class: the validation function. The reason for this is that I can simply combine this with the `bounds`

class into a bounded range, and only need to update the validation to take the bounds into consideration to have a fully functioning class. Well, that, and write suitable constructors, and provide another bounds-checking function.

template <typename T, T lower_, T upper_,
typename L = less_than_comparison::closed<T>,
typename U = less_than_comparison::closed<T> >
class bounded_range : public range<T, L, U>,
public bounds<T, lower_, upper_, L, U>
{
protected:
virtual void throw_if_invalid(const T& mini, const T& maxi)
{
range<T, L, U>::throw_if_invalid(mini, maxi);
if (!in_bounds(mini))
throw std::invalid_argument("Minimum out of bounds");
if (!in_bounds(maxi))
throw std::invalid_argument("Maximum out of bounds");
}
public:
typedef typename T type;
bounded_range()
: range(lower_bound(), upper_bound())
{}
bounded_range(const T& min, const T& max)
: range(min, max)
{
throw_if_invalid(min, max);
}
bounded_range(const range<type>& other)
: range(other)
{
throw_if_invalid(other.get_minimum(), other.get_maximum());
}
using bounds<T, lower_, upper_>::in_bounds;
static bool in_bounds(const range<T>& other)
{
return in_bounds(other.get_minimum()) &&
in_bounds(other.get_maximum());
}
};

[1] The C++ language also permits address types (pointer or reference) as non-type parameters, provided they’re known at compile time, but for that loophole to provide a way to implement `static `

bounds checking with `float `

or, say, `std::point `

types, would, if at all possible, require a mastery of template metaprogramming magic that is far beyond my meagre abilities.

Back

Tagged:

bounds,

C++,

template