Currently, Range assumes that the difference between two ValueTypes is still a ValueType.
That certainly is true for all primitive numeric types (int, float, etc.), but there are cases where that is not the case. One such example is the Time class in JUCE, where:
Time - Time -> RelativeTime
And yet a Range <Time> object would totally make sense: it would work out of the box for all the functionality to check if another Time object is within range, etc.
That breaks where Range tries to fit the difference of two Time objects in another Time object, for example:
constexpr inline ValueType getLength() const noexcept { return end - start; }
what I propose is to add a second DistanceType parameter to the Range, which defaults to the type that results as the difference between two ValueTypes.
template <typename ValueType,
typename DistanceType = decltype (ValueType() - ValueType ())>
class Range
...
and use it as the type of the differences between ValueTypes (as in getLength() above) and as the type of the offsets to be applied to ValueTypes, for example:
[[nodiscard]] constexpr Range expanded (DistanceType amount) const noexcept
{
return Range (start - amount, end + amount);
}
None of the expressions currently used in Range need any change. The only change is in the type of some parameters and return values.
To make things safer, the required relationship between ValueType and DistanceType can be formalized with two static asserts:
// requirement #1: ValueType - ValueType -> DistanceType
static_assert (std::is_convertible <decltype (ValueType () - ValueType()), DistanceType>::value);
// requirement #2: ValueType + DistanceType -> ValueType
static_assert (std::is_convertible <decltype (ValueType () + DistanceType()), ValueType>::value);
