Using Types to Your Advantage

For this post we’re going to stray a bit from both the philosophy of classes and concrete implementation details. Let’s talk a bit about how we can use the compiler to catch logical errors at compile time. In the process I can finally offer an explanation for why a lot of my Math classes are templates even though the type parameter is never used.

All of this stems from some really painful bugs I had in earlier Physics Engines when I wasn’t able to properly specify my frame of reference. If you’re not familiar with this term, consider a simple example where you and I are both standing next to one another, facing in the same direction. If we’re given the instruction to “turn right” we would both turn the same way and there would be no problems. But what happens if we’re standing facing one another? Now if we both “turn tight” I would turn right, but you would turn left. Of course you would insist that it was I who had turned in the wrong direction and we’d have to argue for a while.  This is because in the first example we both share the same (well, same for this example) frame of reference, but in the second example we do not. This is all well and fun in the real world, but in Physics simulations it leads to subtle (and not so subtle) bugs.

So for things like rotations and movement in a Physics Engines, we need some way to specify when we’re moving relative to some global frame of reference, or a local one. When I first encountered this problem I tried to solve it by creating explicitly named functions like this:

void rotate_global( const Vector& axis, float angle );
void rotate_local( const Vector& axis, float angle );

This seems like it should work, but if I’m always using a plain old Vector it’s up to me (and some comments) to remember if that Vector refers to my local frame of reference, or the global one. And since the compiler doesn’t know which one it is, there’s no way for it to tell me if I’m using a local Vector in a function that expects a global. And indeed, in many cases I made exactly this mistake. And when your tests are basic and everything is pointing in the same direction (ie the local and global frame of references point in the same direction), there is no bug. So all your tests pass, but then when you start trying to fly your spaceship around in a video game, it starts to behave oddly the more it turns.

What you need is some way to have two Vector classes, one for Global and one for Local frames of reference. You could try cutting and pasting your Vector class to create a Local_vector and Global_vector, but it’s not hard to imagine how that would become a maintenance nightmare fairly quickly. So how to have the compiler write two copies of the same class? Templates of course.

The end result is what you see in my code. Most of the basic Math types are defined as templates. Then they are explicitly instantiated with a different integer that is completely ignored. As far as you and I are concerned, they are the same thing. But as far as the compiler is concerned, they are completely separate types, and mixing them is an error. Now in our code we can work with rotations Vectors that have the frame of reference built into the type, and our rotation functions can look like:

void rotate( const Global_vector& axis, float angle );
void rotate( const Local_vector& axis, float angle );

It might be worth pointing out that I did not figure out this C++ type magic on my own. People have been coming up with much cleverer ways to assign types to simple values for years now. I remember reading a technique to teach the compiler the difference between a distance and a velocity such that if you tried to add them it would be a compiler error. My usage of the trick is mundane, but extremely helpful in keeping out the bugs. Better yet, it has no runtime cost, which is important in a Physics Engine. The only downside is that some of the template code can be a bit unpleasant to look at (like the friend definitions). I think the upsides clearly outweigh the downs, and now, at last, you know why my Vectors are templates.

Examples

 << Rotations Contents  Coordinate Systems >>