display | more...

We know from C++: Computing Fibonacci numbers at compile time that C++ can do arithmetic at compile time. But, by employing some simple number theory, we can expand this into doing something far more useful: implementing a unit system in C++.

To begin: what do we need? What are units? All SI units (which is what we'll do here) can be expressed as a numerator and a denominator, where each is made up of a combination of orthogonal symbols, representing the different units (i.e. seconds, meters, etc.).

We definitely need a template class representing a unit, because there are infinitely many combinations of these units, and we want to be able to combine them. From the previous paragraph, we probably want two template parameters, one for the numerator and one for the denominator. But what data type should these be? Another sort of class? It would seem so, but....

Recall from number theory that any integer can be uniquely represented by a product of powers of primes, one of Euclid's most-used theorems. That sounds like an integral combination (the powers) of an orthogonal basis (the primes) to me. So our answer to what type the template parameters should be: int!

(Note, this orthogonality of primes is one of the things Kurt Gödel used in his famous incompleteness theorem).

Oh, no. It seems we have run in to a little predicament. There is no way we are going to get C++ to factor numbers at compile time! It's actually not a problem, as we don't need to know what the constitutent basic elements are, just whether they are equal. And for that, we need to reduce our two numbers into lowest terms. And for that, we need a GCD function:

    template<int A, int B>
    struct GCD {
      // Euclid's algorithm.
      static const int val = GCD<B, A % B>::val;

    template<int A>
    struct GCD<A, 0> {
      static const int val = A;

There we go, compile time Greatest Common Divisor. We need one more thing: a template class (like the one above) that knows how to multiply fractions and reduce them to lowest terms. No problem:

    template<int N1, int D1, int N2, int D2>
    struct Mult {
      static const int gcd = GCD<N1*N2, D1*D2>::val;
      static const N = N1 * N2 / gcd;
      static const D = D1 * D2 / gcd;

And finally, the units class itself:

    template<int N, int D = 1>
    class Unit
        Unit(float vin) : val(vin) { }

        Unit operator + (const Unit& ref) {
          return val + ref.coeff();
        Unit operator - (const Unit& ref) {
          return val - ref.coeff();

        template<int Np, int Dp>
        Unit< Mult<N, D, Np, Dp>::N, Mult<N, D, Np, Dp>::D >
              operator * (const Unit<Np, Dp>& ref) {
          return val * ref.coeff();

        template<int Np, int Dp>
        Unit< Mult<N, D, Dp, Np>::N, Mult<N, D, Dp, Np>::D >
              operator / (const Unit<Np, Dp>& ref) {
          return val / ref.coeff();

        operator float () const { return val; }
        float coeff() const { return val; }

        float val;

That's it. For how much code volume there is, it's surprisingly simple. Now, we just need to define some units (they have to be prime, remember?).

    const int kilogram = 2;
    const int meter    = 3;
    const int second   = 5;

Now, if we try some things:

    int main()
      // Distance = Speed * Time
      Unit<meter> d = Unit<meter,second>(5) * Unit<second>(2);
      cout << d << endl;     // 10

      // Acceleration = Force / Mass      
      Unit<meter, second*second> a = 
             Unit<kilogram*meter, second*second>(2.5) / Unit<kilogram>(2);
      cout << a << endl;     // 1.25

      // Speed != Distance * Time
      Unit<meter, second> v = 
             Unit<meter>(10) * Unit<second>(2);  // Compile Error!!

I'm not sure whether it would be possible to design a similar class in which you can define combinational constants/classes like Newtons (kg m / s2), and use them in declarations something like:

    typedef Unit<kilogram*meter, second*second> Newton;
    Unit<Newton * meter> E = m * c * c;

I would be delighted to see one, however.

Log in or register to write something here or to contact authors.