C++ Units
by Calum Grant

Units home page: http://calumgrant.net/units

Introduction

This library is for engineers and scientists who deal with physical quantities.  Units provide a safety net that check the validity of formulae at compile-time and ensure that different units are converted where necessary.  A large number of physical quantities are provided by the library, including all SI units.

This library is for software engineers.  By giving a quantity a unit, it prevents the wrong value being assigned to the wrong thing, or passing the wrong value to the wrong argument of a function.  Units documentation a program - int is not descriptive, but apples is.

The library uses templates and generative programming techniques to handle units at compile time, so there is no run-time overhead.  Many units are predefined and it is straightforward to add your own.

The library is installed by copying the file units.hpp into your include directory.  To use the library

#include "units.hpp"

Using built in units

The built in physical quantities are in the namespace units::values, so for the purposes of this tutorial, assume that

using namespace units::values;

has been written somewhere near the top of the file.  This namespace gives you access to the following physical quantities:

The naming convention is to use the symbol of the SI unit.  For other units, the name is used since there are no standard symbols.

The built-in values are all doubles, however you can change this (see next section).  The physical quantities behave exactly as normal doubles, except that they check the units and perform conversion where necessary.

A value can be constructed from a number, another value, or the default constructor which initializes the value to zero.  When constructing from another value, the other value must be compatible or you will get a compile-time error:

m x;		// x = 0
m y(10);	// y = 10
m z(cm(250));	// z = 2.5
m w(s(3));	// Compile-time error: incompatible units

kph(mph(70))	// Convert miles per hour to km per hour

Assignment is only possible from another value, and the value must be compatible:

x = 10;         // Compile-time error: 10 of what?
x = cm(200);    // Ok: x = 2.0
x = s(3)        // Compile-time error: incompatible units

The number in the value can be obtained using the get() method:

x.get();        // Get current value of x
cm(m(5)).get()  // 500

The normal arithmetic operators are supported: +, -, *, /, /=, *=, ++, --.  There are some restrictions however.  You can only add or subtract compatible values.  You can multiply or divide by any other value, which returns a value with a unit with the correct type (multiplied or divided).  The *= and /= operators can only take a number (in general if you multiply by another unit the type will be different).  e.g.

miles x = m(1) + cm(10);	// Add two distances
m2 area = foot(10) * yard(3);	// Converts to meters squared
s timer;
timer += 10;			// Compile-time error: add 10 of what
timer += s(10);			// Ok: add 10 seconds to timer
liter v = hectare(1) * mm(1);	// Ok
H h(  m(4)*m(8)*kg(2)/s(2)/s(4)/A(1)/A(2) ); // A complex (but valid) formula
s(10) + m(4);			// Compile-time error: Can't add time and distance

The comparison operators (==, !=, <, <=, >, >=) also convert between units implicitly:

minutes(2) > s(70)     	// true

Writing a value to a stream (using operator <<) displays the unit after the value.  Many of the built in units have names, otherwise the text is generated, as in the following example:

    std::cout << "Flow rate is " << m3(mile(1)*inch(80)*foot(9))/s(minute(5));
    // Output: Flow rate is 29.9026 (m)^3.(s)^-1

The units::sqrt function provides a square root - which also takes the root of the unit of course.

m a, b, c;
c = units::sqrt( a*a + b*b );

You can take an arbitrary rational power of a number using the units::raise template, however you must specify the power at compile time so that the compiler knows the unit of the return value. e.g.

units::raise<3,1>(m(2)) == m(2)*m(2)*m(2)

There are some trigonometric functions (units::sin, units::cos and units::tan) which take an angle.  You can supply any unit of angle to the function and the function will convert to radians.  e.g.

units::tan( degree(45) );

There are a number of constants available in the units::constants namespace.  These are

Example:

N attractive_force = units::constants::G * kg(1) * kg(2) / m(3) / m(5);

Creating new units

Values with units are provided by the units::value<> class template, declared as follows: 
template<typename Value, typename Unit>
class value; 

The predefined units (as used in the previous section) are declared in the units::units namespace.  So you can reuse any of these units but provide a different type, e.g.

units::value<float, units::units::m> length;

The unit can be any type, so to create a new unit, just create a new type:

struct apples;
struct oranges; 
typedef units::value<int, apples>  apples_t; 
typedef units::value<int, oranges> oranges_t; 
The new value will be protected from interoperating with naked numbers or other types of unit:
apples_t n(5); 
n = oranges_t(3);        // Compile-time error 
n = apples_t(10);        // Ok 

Displaying units

When a value is output (using operator<<), it appends the unit to the stream.  By default the unit will display the text "units".  You can change this by declaring the name of the unit with the UNITS_DISPLAY_NAME macro, which takes the unit as its first parameter, and its name as its second parameter.  e.g.

UNITS_DISPLAY_NAME( apples, "apples" ); 
UNITS_DISPLAY_NAME( oranges, "oranges" ); 
std::cout << oranges_t(2);   // Output: 2 oranges

Converting between units

A unit can be defined in terms of other units.  The templates units::scale, units::translate, units::pow and units::compose provide a means of constructing new units which can be converted from and to another unit.

The units::scale<> template constructs a unit which is a multiple of another:

struct penny;
typedef units::scale<penny, 4> farthing;      // Multiply penny * 4 to get farthings
typedef units::scale<penny, 1, 12> shilling;  // Multiply penny by 1/12 to get shillings
typedef units::scale<shilling, 1, 20> pound;
typedef units::scale<penny, 1, 30> half_crown;
typedef units::scale<half_crown, 1, 2> crown;
std::cout << "There are " << penny(pound(1)).get() << " old pence in the pound\n";

The units::translate<> template constructs a unit which is offset from another:

typedef units::translate<units::units::K, -27315, 100> Celcius;   
// Celcius = K - 27315/100

The units::pow<> template constructs a unit which is a power of another unit:

typedef units::pow<units::units::m, 3> m3;    // One cubic meter

Finally the units::compose<> template creates a unit which multiplies two other units:

typedef units::compose<power, time> energy;
typedef units::compose< units::units::m, units::pow<units::units::s, -1> > meters_per_second;

The conversion operators are able to analyse the type of the unit and generate a conversion function automatically!