distance type. The following comparison shows the differences
between using dimensional and using a simple typedef.
| typedef | dimensional | ||||||||
|---|---|---|---|---|---|---|---|---|---|
typedef double distance; |
dimensional double is not a double, but a distinct type. By default, you can only do a few
operations on a dimensional type, as opposed to everything you can do with a double. Specifically,
int timeout= 10000;It's implicit because of how this value is used that the value 10000 means ten seconds. It has this meaning because the integer argument to
Sleep is treated as a number of milliseconds, and this
value is eventually passed to Sleep.
This is particularly a problem when different places use different units. If this same value were passed to
SetWaitableTimer, for example, it would mean something different, since that function takes an integer representing
100-nanosecond intervals.
A fundimental principle of the dimensional type is that you can not assign a number to it. There is no way to simply turn a number into a dimensional value. You must specify your units. There is no special operator to do this—you simply multiply your value by a known dimensional quantity.
time timeout1= 10*seconds; time timeout2= 10000*microseconds; distance len1= 3.14* Meter; distance len2= 8*Foot + 3*Inch;
To get a number out, the same principle is used. Divide by a known unit, and you will get a dimensionless number.
int t1= timeout / seconds; //get value in seconds int t2= timeout / microseconds; //get value in microseconds
Additionally, a derived type may add member functions for obtaining values more efficiently (division is a rather expensive operation, compared with other CPU primitives). This allows efficient access to the value while still hiding the underlying representation.
extern const int time_base= 1;
class duration : public dimensional<int,&time_base> {
public:
int as_milliseconds() const { return Value; }
int as_seconds() const { return Value/1000; }
};
extern const millisecond= duration::unit();
extern const second= millisecond*1000;
double dist_base= 1.0; typedef dimensional<double,&dist_base> distance; extern const distance Meter= distance::unit();The first line serves two purposes. The fundimental need for it concerns the template mechanism in C++. If you just declared
distance to be a dimensional<double>, it would in fact be the same type as every other
dimensional<double>, that was declared. This pretty much would defeat the purpose of having it as opposed
to a simple typedef. So, a mechanism is needed to make a distinct type.
This is done by using a second, non-type, template parameter. Different values for the second argument will result in different specializations of the template and distinct types. The simplest way to get a unique value and track it across multiple compilation units is to use the address of a global variable.
Now, as long as that parameter is there, it can be co-opted for another purpose. Where does the first dimensional value come from? There is no way to turn a plain number into a distance other than to multiply it by an already known distance. So where does the first one come from?
This brings us to the third line. The static member unit returns a dimensional value of that type.
Being a member, it can do things with the class that you can't, so it can get around the chicken-and-egg
problem quite easily. The underlying value inside that object is the value found in the first line. It doesn't have
to be 1.0. In general, it's whatever you want the representation of your base unit to be.
Once the first unit exists, you can declare other units in terms of the existing ones.
extern const distance Millimeter= Meter/1000; extern const distance Inch= 25.4*Millimeter; extern const distance Foot= 12.000024*Inch;