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