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
{
public:
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; }
private:
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.