Using the compiler to find bugs

During session 2, I discussed a couple of techniques to use the static type system to let the compiler find my bugs. Here is a “short” summary of the code we created during the session.

For our first problem, assume that you are creating a small library for terminal 2D-graphics (because that’s a very novel idea…). You create a function with the following signature to let the user print a character at a specific coordinate:

As long as the user calls this function with coordinates in the x,y-plane, this will work fine, but what if the user doesn’t read our (of course excellent) documentation and instead tries to call it with a row and column? The compiler would even allow for this stupid mix-up:

The first step of helping the user (and ourselves) is to add an extra level of abstraction!

This extra type makes it difficult for the user to call our function with it’s parameters reordered. The user also get the extra bonus of having our Point type to use locally.

Of course, the user can still use it in stupid ways:

We could of course have made Point into some advanced class with private data members, but it doesn’t really make sense here; Point doesn’t have any invariants to keep track of! We could make it easier to use though. Lets start by adding a couple of reference members so that I can access y as row and x as column:

We can still initialize a point with values for only x and y and the compiler will initialize the references according to our default values. It is, however, not possible to only initialize a Point by only providing row and column 🙁

What I want to do now is to make the following construct valid:

Now, this doesn’t really look like C++, but it is possible to implement. Assume that there are variables named row and column available in this scope and that we can assign integers to those variables, then this would work. The question is what type should those variables have? The simplest solution would be to make them int. This would however not work since we already have a constructor that accepts two int parameters – the one that initializes x and y. Let’s instead create new types that fulfills the requirement above.

Now we create our variables and add a constructor to Point:

We could of course hide the value member of Row_Type by adding a conversion operator:

If we make the same change to Column_Type, we can change our Point constructor accordingly:

The next problem I wanted to “fix” was to create a wrapper for integral types that makes sure that we won’t go outside a pre-defined range of the type. Our goal at the beginning was to have a type that supported addition with positive integers and will throw an exception iff the addition makes the value go out of range.

To test our code we use the excellent catch library. When we are done, we want this test case to pass

Lets start with the basics, a template class that can be constructed from a number of underlying type and can be converted back to the underlying type implicitly.

Now it’s time to add addition. I usually like to implement it by first implementing compound addition. In this example, we’ll just support addition with a positive integer, for a full implementation we’ll have to take care of addition with negative values as well…

So, great, we now have a type that represent an enumerable type that makes sure the internal value is in the given range. Now lets make it a bit more usable and make it possible to change the addition behavior. Instead of throwing, we might want to clamp the value. To fix this, we’ll move the addition behavior to a policy class. This policy has to be a template class (or possibly have a templated function that does the addition).

Now we can add a new policy for clamping:

With this modification, the following test case should pass.

To help the user, default values for template arguments could be nice.

We could even add templated aliases.

Now we have a type that allows the user to have a safe type for integral values. It’s rather simple to modify the behavior by adding a new policy and best of all, it’s still really effective. Check out the full example on godbolt!

Posted by Eric Elfving

C++-mentor och mjukvaruarkitekt på SAAB