
I have a suggestion for a simple Boost feature - subranges. For those of you who may not know, subranges are a Pascal idea. Using un-restricted numeric types such as ints, shorts and floating point types is somewhat dangerous. For example, if you write a function that, given a date, returns the number of the day in the year that date falls on, what type do you return? Most C/C++ programmers would automatically use an int but ints can be negative and -1 as a day of the year is meaningless. So use unsigned types. But then 0 is invalid, as is any value above 365. Subranges to the rescue... In pascal one would write type DayOfYear = 1..365; function NthDayOfYear(const d : Date) : DayOfYear; The type statement declares a new data type that is restricted to the range 1 to 365 and this is used as the return type of the function. If NthDayOfYear has a fault that sometimes generates a value outside this range, you'll get a runtime error. The idea is to have range checking switched on during development, but to switch if off for release code for efficiency reasons, something that all good pascal compilers can do. My own personal experience having used pascal type languages with this feature is that it's very, very useful indeed. On a number of projects, subranges have illuminated obscure bugs in my code that I might otherwise have missed. After having switched to C++ from Pascal, I soon found myself missing subranges so I wrote a class template that emulates them: template <typename T, T min, T max> class subrange; This would contain the usual operators/constructors etc. that I've omitted for shortness. The above example would now become in C++: typedef subrange<int, 1, 365> day_of_year; day_of_year NthDayOfYear(const Date& d); The class is very simple - all it does is store a value and then checks to make sure it's always in the range min to max when you assign variables of type T to it, do arithmetic and so on and throws an exception if the new value is out of range. A preprocessor symbol is used to switch this range checking on or off as required so that you can release code without the checking overhead. The big question is, do any of you think this is a useful enough feature? Regards, Brian

HI, Brian Martin wrote:
I have a suggestion for a simple Boost feature - subranges. [...] The big question is, do any of you think this is a useful enough feature?
Yes, it is. What's more, the biggest gain (in my humble opinion, of course) is not the bare fact that you can have range checking at run-time, but that it is possible to provide range checking at *compile-time*, thus strengthening the type system. Consider: void fun(range<0, 100> i) { // do sth with i, being sure that it falls within [0, 100) range } // ... range<0, 200> i(200); fun(i); // this should NOT compile int j; cin >> j; range<0, 100> k(j); // this should compile, but do run-time check For your amusement, please take a look at the code I've written recently (I do not use it, it was just to explore the idea): #include <iostream> #include <stdexcept> template <bool> struct static_assert; template <> struct static_assert<true> {}; template <long L, long U> class range { public: static long check(long v) { if (v < L || v >= U) throw std::range_error("range exceeded"); return v; } range(long v) : v_(check(v)) { static_assert<L < U>(); } template <long L2, long U2> range(range<L2, U2> rhs) : v_(rhs.get()) { static_assert<L2 >= L && U2 <= U>(); } range & operator=(long v) { v_ = check(v); return *this; } template <long L2, long U2> range & operator=(range<L2, U2> rhs) { static_assert<L2 >= L && U2 <= U>(); v_ = rhs.get(); return *this; } range & operator++() { check(++v_); return *this; } range operator++(int) { range t(*this); check(++v_); return t; } range & operator--() { check(--v_); return *this; } range operator--(int) { range t(*this); check(--v_); return t; } long get() const { return v_; } void set(long v) { v_ = check(v); } enum { lBound = L, uBound = U }; private: long v_; }; template <long L1, long U1, long L2, long U2> range<L1, U1> range_cast(range<L2, U2> r) { static_assert<L2 <= U1 && U2 >= L1>(); return range<L1, U1>(r.get()); } template <long L1, long U1, long L2, long U2> range<L1 + L2, U1 + U2> operator+(range<L1, U1> lhs, range<L2, U2> rhs) { return range<L1 + L2, U1 + U2>(lhs.get() + rhs.get()); } template <long L1, long U1, long L2, long U2> range<L1 - U2, U1 - L2> operator-(range<L1, U1> lhs, range<L2, U2> rhs) { return range<L1 - U2, U1 - L2>(lhs.get() - rhs.get()); } template <long L1, long L2> struct static_minmax2 { enum { min_val = L1 < L2 ? L1 : L2, max_val = L1 > L2 ? L1 : L2}; }; template <long L1, long L2, long L3 = L1, long L4 = L1> class static_minmax { private: enum { t1 = static_minmax2<L1, L2>::min_val, t2 = static_minmax2<L3, L4>::min_val, t3 = static_minmax2<L1, L2>::max_val, t4 = static_minmax2<L3, L4>::max_val}; public: enum { min_val = t1 < t2 ? t1 : t2, max_val = t3 > t4 ? t3 : t4}; }; template <long L1, long U1, long L2, long U2> range < static_minmax<L1 * L2, L1 * U2, U1 * L2, U1 * U2>::min_val, static_minmax<L1 * L2, L1 * U2, U1 * L2, U1 * U2>::max_val
operator*(range<L1, U1> lhs, range<L2, U2> rhs) { return range < static_minmax<L1 * L2, L1 * U2, U1 * L2, U1 * U2>::min_val, static_minmax<L1 * L2, L1 * U2, U1 * L2, U1 * U2>::max_val
(lhs.get() * rhs.get()); }
template <long L1, long U1, long L2, long U2> range < static_minmax<L1 / L2, L1 / U2, U1 / L2, U1 / U2>::min_val, static_minmax<L1 / L2, L1 / U2, U1 / L2, U1 / U2>::max_val
operator/(range<L1, U1> lhs, range<L2, U2> rhs) { return range < static_minmax<L1 / L2, L1 / U2, U1 / L2, U1 / U2>::min_val, static_minmax<L1 / L2, L1 / U2, U1 / L2, U1 / U2>::max_val
(lhs.get() / rhs.get()); }
// and later (commented code should not compile): #define assert(c) if (c); else { std::cerr << "assertion failed at " << __LINE__ << '\n'; } int main() { try { range<0, 100> i(4); long l = i.get(); assert(l == 4); i = 56; assert(i.get() == 56); i.set(45); assert(i.get() == 45); range<0, 50> i2(10); i = i2; assert(i.get() == 10); range<0, 200> k1(i + i2); //range<0, 40> k2(i - i2); range<30, 350> i3(35); i = range_cast<0, 100>(i3); i = 98; i++; //++i; range<10, 20> i4(15); range<30, 40> i5(35); //i3 = range_cast<30, 350>(i4 * i5); std::cout << (i5 / i4).lBound << '-' << (i5 / i4).uBound << '\n'; } catch (const std::exception &e) { std::cout << "error: " << e.what() << '\n'; } } -- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/
participants (2)
-
Brian Martin
-
Maciej Sobczak