Defining Constrained Value Types

Problem

You want self-validating numerical types to represents numbers with a limited range of valid values such as hours of a day or minutes of an hour.

Solution

When working with dates and times, frequently you will want values that are integers with a limited range of valid values (i.e., 0 to 59 for seconds of a minute, 0 to 23 for hours of a day, 0 to 365 for days of a year). Rather than checking these values every time they are passed to a function, you would probably prefer to have them validated automatically by overloading the assignment operator. Since there are so many of these types, it is preferable to implement a single type that can handle this kind of validation for different numerical ranges. Example 5-10 presents a ConstrainedValue template class implementation that makes it easy to define ranged integers and other constrained value types.

Example 5-10. constrained_value.hpp

#ifndef CONSTRAINED_VALUE_HPP
#define CONSTRAINED_VALUE_HPP

#include 
#include 

using namespace std;

template
struct ConstrainedValue
{
 public:
 // public typedefs
 typedef typename Policy_T policy_type;
 typedef typename Policy_T::value_type value_type;
 typedef ConstrainedValue self;

 // default constructor
 ConstrainedValue( ) : m(Policy_T::default_value) { }
 ConstrainedValue(const self& x) : m(x.m) { }
 ConstrainedValue(const value_type& x) { Policy_T::assign(m, x); }
 operator value_type( ) const { return m; }

 // uses the policy defined assign function
 void assign(const value_type& x) {
 Policy_T::assign(m, x);
 }

 // assignment operations
 self& operator=(const value_type& x) { assign(x); return *this; }
 self& operator+=(const value_type& x) { assign(m + x); return *this; }
 self& operator-=(const value_type& x) { assign(m - x); return *this; }
 self& operator*=(const value_type& x) { assign(m * x); return *this; }
 self& operator/=(const value_type& x) { assign(m / x); return *this; }
 self& operator%=(const value_type& x) { assign(m % x); return *this; }
 self& operator>>=(int x) { assign(m >> x); return *this; }
 self& operator<<=(int x) { assign(m << x); return *this; }

 // unary operations
 self operator-( ) { return self(-m); }
 self operator+( ) { return self(-m); }
 self operator!( ) { return self(!m); }
 self operator~( ) { return self(~m); }

 // binary operations
 friend self operator+(self x, const value_type& y) { return x += y; }
 friend self operator-(self x, const value_type& y) { return x -= y; }
 friend self operator*(self x, const value_type& y) { return x *= y; }
 friend self operator/(self x, const value_type& y) { return x /= y; }
 friend self operator%(self x, const value_type& y) { return x %= y; }
 friend self operator+(const value_type& y, self x) { return x += y; }
 friend self operator-(const value_type& y, self x) { return x -= y; }
 friend self operator*(const value_type& y, self x) { return x *= y; }
 friend self operator/(const value_type& y, self x) { return x /= y; }
 friend self operator%(const value_type& y, self x) { return x %= y; }
 friend self operator>>(self x, int y) { return x >>= y; }
 friend self operator<<(self x, int y) { return x <<= y; }

 // stream operators
 friend ostream& operator<<(ostream& o, self x) { o << x.m; return o; }
 friend istream& operator>>(istream& i, self x) {
 value_type tmp; i >> tmp; x.assign(tmp); return i;
 }

 // comparison operators
 friend bool operator<(const self& x, const self& y) { return x.m < y.m; }
 friend bool operator>(const self& x, const self& y) { return x.m > y.m; }
 friend bool operator<=(const self& x, const self& y) { return x.m <= y.m; }
 friend bool operator>=(const self& x, const self& y) { return x.m >= y.m; }
 friend bool operator==(const self& x, const self& y) { return x.m == y.m; }
 friend bool operator!=(const self& x, const self& y) { return x.m != y.m; }
 private:
 value_type m;
};

template
struct RangedIntPolicy
{
 typedef int value_type;
 const static value_type default_value = Min_N;
 static void assign(value_type& lvalue, const value_type& rvalue) {
 if ((rvalue < Min_N) || (rvalue > Max_N)) {
 throw range_error("out of valid range");
 }
 lvalue = rvalue;
 }
};

#endif

The program in Example 5-11 shows how you can use the ConstrainedValue type.

Example 5-11. Using constained_value.hpp

#include "constrained_value.hpp"

typedef ConstrainedValue< RangedIntPolicy<1582, 4000> > GregYear;
typedef ConstrainedValue< RangedIntPolicy<1, 12> > GregMonth;
typedef ConstrainedValue< RangedIntPolicy<1, 31> > GregDayOfMonth;

using namespace std;

void gregOutputDate(GregDayOfMonth d, GregMonth m, GregYear y) {
 cout << m << "/" << d << "/" << y << endl;
}

int main( ) {
 try {
 gregOutputDate(14, 7, 2005);
 }
 catch(...) {
 cerr << "whoops, shouldn't be here" << endl;
 }
 try {
 gregOutputDate(1, 5, 1148);
 cerr << "whoops, shouldn't be here" << endl;
 }
 catch(...) {
 cerr << "are you sure you want to be using a Gregorian Calendar?" << endl;
 }
}

The output from the program in Example 5-11 is:

7/14/2005
are you sure you want to be using a Gregorian Calendar?

 

Discussion

Constrained value types are particularly relevant when working with dates and times, because many values related to date/times are integers that must occur within a specific range of values (e.g., a month must be in the interval [0,11] or a day of the month must be in the interval [0,30]). It is very time consuming and error prone to manually check that every function parameter fits into a certain range. Just imagine if you wanted to make a global change to how a million line program handled date range errors!

The ConstrainedValue template class when used with a RangedIntPolicy template can be used to define easily several different types that throw exceptions when assigned values out of range. Example 5-12 shows some different examples of how you can use ConstrainedValue to define new self-validating integer types.

Example 5-12. More of usage of ConstrainedValue

typedef ConstrainedValue< RangedIntPolicy <0, 59> > Seconds;
typedef ConstrainedValue< RangedIntPolicy <0, 59> > Minutes;
typedef ConstrainedValue< RangedIntPolicy <0, 23> > Hours;
typedef ConstrainedValue< RangedIntPolicy <0, 30> > MonthDays;
typedef ConstrainedValue< RangedIntPolicy <0, 6> > WeekDays;
typedef ConstrainedValue< RangedIntPolicy <0, 365 > > YearDays;
typedef ConstrainedValue< RangedIntPolicy <0, 51> > Weeks;

The ConstrainedValue template class is an example of policy-based design. A policy is a class passed as a template parameter that specifies aspects of the implementation or behavior of the parameterized type. The policy passed to a ConstrainedValue is expected to provide the implementation detail of how to assign between the same specializations of the type.

Using policies can improve the flexibility of classes by deferring design decisions to the user of the type. It is common to use policies when a group of types has a common interface but vary in their implementation. Policies are also particularly useful when it is impossible to anticipate and satisfy all possible usage scenarios of a given type.

There are many other policies you can possibly use with a ConstrainedValue type. For instance, rather than throw an exception, you may choose to assign a default value, or assign the nearest legal value. Furthermore, constraints don't even have to be ranges: you might even have a constraint that a value is always even.

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net