In Software for Infrastructure, Bjarne Stroustrup shows a way to use templates to make the C++ compiler aware of the unit of a value (e.g. kilograms, seconds, etc.), such that it can check consistent use and prevent disasters like the well known error at NASA in 1999 caused by mixing incompatible units. In this article, I show how to extend this idea to support any number of base units and linearly related units (e.g. centimetres, metres and inches) by teaching the compiler how to do arithmetic on units.

Metres, Seconds, and Kilograms

Stroustrup uses a class template Unit to represent a unit made from powers of metres, kilograms and seconds:

// metre^M * kilogram^K * second^S
template<int M, int K, int S> struct Unit;

So, Unit<1,0,0> represents metre, Unit<0,1,0> is kilogram, Unit<1,1,0> is kilogram (times) metre, and Unit<1,0,-1> is metre per second:

using Metre          = Unit<1, 0, 0>;
using Kilogram       = Unit<0, 1, 0>;
using Second         = Unit<0, 0, 1>;
using MetrePerSecond = Unit<1, 0,-1>;

Values have this unit type as a template parameter:

template<typename Unit> struct Value { double val; /* ...*/ };

Only values with the same unit can be added or subtracted. (It wouldn’t make sense to add 2 metres to 5 seconds, would it?) Multiplying and dividing is always allowed, the units simply also have to be multiplied or divided. (10 metres divided by 5 seconds should just give 2 metre per second.) For multiplication or division, the M, K, and S values of the units just need to be added or subtracted, respectively:

// Multiplication
(metre^M1 * kilogram^K1 * second^S1) * (metre^M2 * kilogram^K2 * second^S2)
 = metre^(M1+M2) * kilogram^(K1+K2) * second^(S1+S2)

// Division
(metre^M1 * kilogram^K1 * second^S1) / (metre^M2 * kilogram^K2 * second^S2)
 = metre^(M1-M2) * kilogram^(K1-K2) * second^(S1-S2)

(For example Unit<1,0,0> (metre) divided by Unit<0,0,1> (second) results in Unit<1,0,-1> (metre per second).)

Making all the right operator overloads to automatically give the right result isn’t very hard. This way, when correctly used, the compiler will just complain there is no operator+ for Value<Second> and Value<Kilogram> when you try to add seconds to kilograms.

Volt, Newton, and Ohm

The above example for three SI units is very simple to extend to all seven SI units (Kelvin, Second, Metre, Kilogram, Candela, Mole, and Ampere):

template<int Kel, int Sec, int Met, int Kg, int Can, int Mol, int Amp>
struct Unit;

With this, every derived SI unit can be represented. Volt, for example, would be Unit<0,-3,2,1,0,0,-1>, since, as we all know, volt is kilogram square metre per ampere cubic second. (Volt is watt per ampere, watt is joule per second, joule is newton metre, newton is kilogram metre per square second.)

Kilometres, Litres, and Inches

With the 7-integer Unit template, we can represent ampere, candela, coulomb, farad, gray, henry, hertz, joule, katal, kelvin, kilogram, lumen, lux, metre, square metre, cubic metre, metre per second, metre per square second, mole, newton, ohm, pascal, second, siemens, tesla, volt, watt, and weber. But, what about Kilometres, litres, and inches?

Of course, we could just force the user to always use SI units and represent any length in metres, any time duration in seconds, and so on. However, it would be quite convenient if our unit system could also handle (linearily) scaled versions of all SI units. An easy way to support this is to add another template parameter to the Unit template to represent by how much the SI unit is scaled. To avoid rounding errors (and because floating point types can’t be used as template parameters anyway), we use two integers which together represent a rational number (numerator/denomerator):

template<int Num, int Denom, int Kel, int Sec, int Met, int Kg, int Can, int Mol, int Amp>
struct Unit;

using Kilometre = Unit<1000, 1, 0, 0, 1, 0, 0, 0, 0>; // 1000 metre
using Litre = Unit<1, 1000, 0, 0, 3, 0, 0, 0, 0>; // 1/1000 metre^3
using Inch = Unit<254, 10000, 0, 0, 1, 0, 0, 0, 0>; // 254/10000 metre

(Or, if you like std::ratio, define Unit with template<typename Scale, int Kel, int Sec ..., and use Unit<std::ratio<1000,1>, 0, 0, ....)

The template magic for calculating the resulting unit of a multiplication or division gets a little trickier as it now also has to handle the scale as well, a fun challenge, but still quite doable.

We have to make sure to always use the lowest possible terms for the numerator and denumerator, to avoid making the compiler think 200/2 metre is something else than 100/1 metre. To do this, we add more template magic to reduce the ratios automatically.

Pixels, Bytes and Bits

So, we can make a SI unit-aware system in C++ without any runtime costs to get scientists to make less errors when using C++. But what about ourselves? Most of us aren’t working very often with kilograms or candelas when programming. We are working with pixels, bytes, bits, characters, and other stuff the SI unit system doesn’t say anything about. I would very much like my compiler to yell at me when I’m mixing bytes per pixel with bits per pixel instead of getting undefined behaviour when I’m accidentally reading 24 bytes instead of 3.

We could of course extend the SI system with ‘our’ units, by adding even more parameters to the Unit template:

template<
	int Num, int Denom, int Kel, int Sec, int Met, int Kg,
	int Can, int Mol, int Amp, int Pixel, int Byte, int Char
>
struct Unit;

But this is getting pretty ugly and hard to change again when we find yet another unit we want to use. How about a more generic solution? The first thing that comes to mind is to use a C++11 variadic template:

template<int Num, int Denom, int... Whatever>
struct Unit;

The template magic to calculate resulting units gets yet a little harder, but is still a fun challenge. Now, we have to assign a fixed position to every unit we want to use. For example, when we use seconds, characters and bytes, we might use Unit as Unit<num, denom, second, char, byte>. However, there is nothing that checks we don’t accidentally use the third parameter for anything else than byte. Can we do it in a more generic way?

Cool, Happy and Awesome

A programmer can create any number of type names, and declare them at almost any point in a program. So, let’s try to use type names as basic unit for our unit system:

struct metre {};
struct second {};
struct byte {};
struct cool {}; // Unit for coolness: "That's 17 cool."
struct happy {}; // Unit for happiness: "I'm 10 happy."
struct awesome {}; // Unit for awesomeness: "That's 7 awesome."

template<typename> struct value { double val; };

value<happy> my_happiness{5}; // 5 happy

Happy per Cool, Awesome per Meter, and Byte Ampere

But what now? How do we combine these units?

using happy_per_cool = ...; // What do we put here?
Value<happy_per_cool> x{2}; // Two happy per cool.

There are lots of ways to do this, but in any case the compiler needs to know how happy_per_cool is related to happy and cool to know that when multiplied by prettiness, you get happiness.

We could define templates to combine units such as

// multiply<A, B, C> represents A*B*C
template<typename ...> struct multiply;

// power<A, N> represents A^N
template<typename A, int N> struct power;

// A/B is A*B^-1
template<typename A, typename B> using divide = multiply<A, power<B, -1>>;

But how do we tell at compile time whether multiply<divide<meter, second>, second> and meter are the same?

This is similar to the situation with rational numbers where we have to tell whether 200/2 is the same as 100/1 or not. There, we solved this by only using a standard form: Always reduce the numbers the lowest possible terms for the two integers. That way, 200/2 is reduced to 100/1, and then we can just compare the numbers directly.

Let’s try to apply the same trick to this new unit system. All units are (automatically) reduced to a standard form before using: A single multiplication of powers of non-derived units: multiply<power<A,N1>, power<B, N2>, ... >. However, a multiplication of a single term is always reduced to just that term: multiply<power<A, 2>> reduces to power<A, 2>. Likewise, a power of one of a term is always reduced to just that term: power<A, 1> reduces to A. Powers of zero reduce to nothing. And last but not least, each non-derived type only occurs once in the multiplication, so powers have to be ‘merged’ when reducing: multiply<A, power<A, 2>, B> becomes multiply<power<A, 3>, B>. (So, multiply<divide<metre, second>, second> reduces to metre as expected, when following these rules.)

It’s possible to implement all these rules with C++ template magic. It’s not easy, but it’s quite fun to do:

template<typename T...>
using multiply = typename template_magic::multiply<T...>::reduced_type;

template<typename T, int N>
using power = typename template_magic::power<T, N>::reduced_type;

However, our rules do not make multiply<newton, meter> and multiply<meter, newton> reduce to the same type. The easiest way to make these reduce to the same type would be to add to the rules that the contents of multiply have to be sorted somehow, but unfortunately, this is impossible. There is no way to get a strict ordering on type names at compile time.

So, to check if two units are the same, we shouldn’t use std::is_same<A, B>. Instead, we divide A by B and check whether that reduces to an empty product (the unitless ‘unit’):

template<typename A, typename B>
using is_equal = std::is_same<divide<A, B>, multiply<>>;

Megapixel per Gigabyte, Terabit, and Millicool

To support scaled versions of units (such as megapixel or millicool) in the generalized system, we add yet another template:

// scale<T, N, D> represents (N/D) T
template<typename T, int N, int D> struct scale;

This introduces a lot of problems: Is power<scale<meter, 10, 1>, 2> the same as scale<power<meter, 2>, 100, 1>? We have to add yet another rule to the ‘unit reduction system’: scale is always the outermost template, and is removed when it’s just scaling by one. Yay, more template magic to write, but still not impossible:

template<typename T, int N, int D>
using scale = typename template_magic::scale<T, N, D>::reduced_type;

I’ve implemented all of this, and it seems to work: github/m-ou-se/units

static_assert(is_equal<mega<volt>, divide<watt, micro<ampere>>>::value, ""); // Works :D

Part of the code shows how pretty the SI system is:

struct kilogram {};
struct metre {};
struct second {};
struct ampere {};
struct mole {};
struct kelvin {};
struct candela {};

using square_metre            = multiply< metre           , metre                   >;
using cubic_metre             = multiply< square_metre    , metre                   >;
using metre_per_second        = divide  < metre           , second                  >;
using metre_per_square_second = divide  < metre_per_second, second                  >;
using newton                  = multiply< kilogram        , metre_per_square_second >;
using joule                   = multiply< newton          , metre                   >;
using pascal                  = divide  < newton          , square_metre            >;
using gray                    = divide  < joule           , kilogram                >;
using watt                    = divide  < joule           , second                  >;
using hertz                   = divide  < unit            , second                  >;
using katal                   = divide  < mole            , second                  >;
using volt                    = divide  < watt            , ampere                  >;
using weber                   = multiply< volt            , second                  >;
using henry                   = divide  < weber           , ampere                  >;
using tesla                   = divide  < weber           , square_metre            >;
using coulomb                 = multiply< ampere          , second                  >;
using farad                   = divide  < coulomb         , volt                    >;
using ohm                     = divide  < volt            , ampere                  >;
using siemens                 = divide  < unit            , ohm                     >;
using radian                  = divide  < metre           , metre                   >;
using steradian               = divide  < square_metre    , square_metre            >;
using lumen                   = multiply< candela         , steradian               >;
using lux                     = divide  < lumen           , square_metre            >;

template<typename T> using deca  = scale<T, 10l>;
template<typename T> using hecto = scale<T, 100l>;
template<typename T> using kilo  = scale<T, 1000l>;
template<typename T> using mega  = scale<T, 1000000l>;
template<typename T> using giga  = scale<T, 1000000000l>;
template<typename T> using tera  = scale<T, 1000000000000l>;
template<typename T> using peta  = scale<T, 1000000000000000l>;
template<typename T> using exa   = scale<T, 1000000000000000000l>;

template<typename T> using deci  = scale<T, 1l, 10l>;
template<typename T> using centi = scale<T, 1l, 100l>;
template<typename T> using milli = scale<T, 1l, 1000l>;
template<typename T> using micro = scale<T, 1l, 1000000l>;
template<typename T> using nano  = scale<T, 1l, 1000000000l>;
template<typename T> using pico  = scale<T, 1l, 1000000000000l>;
template<typename T> using femto = scale<T, 1l, 1000000000000000l>;
template<typename T> using atto  = scale<T, 1l, 1000000000000000000l>;

And how ugly the Imperial system is:

using yard          = scale<metre, 9144, 10000>;
using foot          = scale<yard, 1, 3>;
using inch          = scale<foot, 1, 12>;
using line          = scale<inch, 1, 12>;
using pica          = scale<inch, 1, 6>;
using point         = scale<pica, 1, 12>;
using thou          = scale<inch, 1, 1000>;
using chain         = scale<yard, 22>;
using furlong       = scale<chain, 10>;
using mile          = scale<furlong, 8>;
using league        = scale<mile, 3>;
using rod           = scale<chain, 1, 4>;
using link          = scale<rod, 1, 25>;
using fathom        = scale<yard, 2>;
using cable         = scale<fathom, 120>;
using perch         = power<rod, 2>;
using rood          = multiply<furlong, rod>;
using acre          = multiply<furlong, chain>;
using pound         = scale<kilogram, 45359237, 100000000>;
using ounce         = scale<pound, 1, 16>;
using drachm        = scale<pound, 1, 256>;
using grain         = scale<pound, 1, 7000>;
using stone         = scale<pound, 14>;
using quarter       = scale<pound, 28>;
using hundredweight = scale<pound, 112>;
using ton           = scale<pound, 2240>;
// TODO: fluid ounce, gill, pint, quart, gallon, etc.

Kelvin, Celsius and Fahrenheit

With length, speed, mass, and almost any quantity, you don’t need a unit when it’s zero. Zero metres and zero inches are the same, zero kilograms and zero pounds are the same, etc. However, temperatures are the exception I did not yet take into account when implementing the generic unit system. (Apparently not generic enough yet.)

10 degrees Fahrenheit is about -12.22 degrees Celsius when it represents an absolute temperature. However, when used for a relative temperature, it is about 5.56 degrees Celsius. So, “This thing is 10 degrees Fahrenheit.” is the same as “This thing is -12.22 degrees Celsius.”, however, “This thing is 10 degrees Fahrenheit colder than that thing.” is the same as “This thing is 5.56 degrees colder than that thing.” Clearly, relative and absolute temperatures are different things, and the relative versions of Celsius and Fahrenheit have a different relation than the absolute versions.

For relative temperatures, it’s simple:

using relative_celsius = kelvin;
using relative_fahrenheit = scale<relative_celsius, 9, 5>;

But for absolute temperatures, we would need somethiing like translate next to scale:

using absolute_celsius = translate<kelvin, 27315, 100>;
using absolute_fahrenheit = translate<scale<relative_celsius, 9, 5>, 32, 1>;

Some day when I have time I’ll add this feature to my implementaiton. Also, I’ll add a template<typename T, typename Unit> quantity class template such that you can use this compile time unit system in real world applications by replacing things like int speed; with quantity<int, meter_per_second> speed;.

Star, Watch and Comment

If you’re interested in using this units library, please star or watch the GitHub repository. Also, any feedback is greatly appreciated on GitHub or via the comments below.