Is everything we use a string for really just a bunch of characters? Is everything we use an int for really just a number? Probably not. We can have stronger types than that.

Imagine we are programming a role play game. We’ll need something to store our character’s data, like the name, the current level, experience points, attributes like stamina and strength and the amount of gold we own. The usual stuff. It’s simple:

typedef std::tuple< std::string, //name int, //level int, //XP int, //stamina int, //strength int //gold > Character;

Okay, that’s too simple. Nobody would do that. Almost nobody. We hope. Let’s be realistic:

class Character { std::string name; int level; int xp; int stamina; int strength; int gold; };

That’s more like it. Obviously, that class is missing some methods. But let’s concentrate on the variables for now.

Simple types, simple problems

As it stands, we could have a character with 4678285 gold, level 772999566, negative XP and the telling name “meh 56%&8450p&jntr

gr?==) Bobby Tables“.

If you already know little Bobby Tables or clicked the link, you know where I am going with this: We’ll have to check that whenever we create a new character, the values we assign to those attributes have to make sense. XP usually are not negative. A name usually does not contain special characters.

While we’re at it, character creation is not the only time we can mess up those attributes. Add a big negative number to the XP and we get in trouble, too.

Of course, this can be fixed easily: xp should be an unsigned instead of an int , so it can’t be negative. The name should be const because a character can not change its name, and then it only needs to be checked during character creation.

Except that this will fix only very few of all the problems we can run into. unsigned can underflow, giving impossible large amounts of XP. The level probably can only go as far as 70 or 80 or so (70 was the limit when I last played Wolrd of Warcraft), and that’s not a limit any built-in type can give us.

We can left-shift an int – but what does that mean if we calculate character.stamina << 5 ? It does not make any sense – so we’d better not be able to make errors like that in the first place.

Now let’s have a look at one of the methods:

void Character::killMonster(Monster const& monster) { gold += monster.loot(); level += monster.bonusXP(); }

This doesn’t look right – the bonus XP granted by killing the monster probably should be added to the character’s XP, not to its level. The additional gold looks about right unless the loot is calculated in some other monetary unit that has to be converted first.

Simple problems, simple solutions: Use stronger types

The first problem we observed above is that we assigned very general types to variables that had additional semantics. The second was that we used the same general types for variables that have different, incompatible semantics.

A std::string is just a bunch of characters, but a name that has been sanitized to be suitable for an RPG character is much more (and, in some ways, less) than that. An int is just a number, while a monetary amount, points and levels are more than just that.

Strong typedef

The solution is to the exchangeability problem is to use what is commonly called a strong typedef. With a normal C++ typedef, a Level type introduced by typedef int Level still is int – it’s just another name for the same type.

A strong typedef is a completely different type that simply happens to behave the same as its base type, in this case, the int . Strong typedefs are simple wrappers around a variable of their base type.

Thanks to optimizing compilers, those wrappers usually have the same performance as the base types. They do not change the runtime code, but they can prevent a lot of errors at compile time.

Other restrictions

It is relatively simple to write classes that can contain only certain values and provide only operations that do not invalidate them again. For example, a class for a valid character name would need some way to construct such a name from a plain std::string . If we do not allow the insertion of arbitrary chars into a Name and can only assign valid Name objects, that constructor would be the only point where we need to check the validity of a name.

For our XP we could use something like a strong typedef that does not provide subtraction (unless we can actually lose XP) and does not allow bit shifting and other stuff that is nonsense for experience points.

In the end, our character class could look something like this:

class Character { CharacterName name; Level level; ExperiencePoints xp; Attribute stamina; Attribute strength; Gold gold; // ... void killMonster(Monster const& monster) { gold += monster.loot(); // level += monster.bonusXP(); //ERROR - no matching function for operator+(Level, XP) xp += monster.bonusXP(); } };

In addition to the added safety, the explicit type names make the code even easier to read. Compare this to the tuple<std::string, int, int int...> . Of course, this last example is an extreme we probably never go to, but it may be worth exploring the possibilities between that and the lazy way using only built-in types.

Conclusion

If we really look into the things we model in our program, there are many things that are not “just a number” or “just a string”. While it can be a lot of work to define separate, stronger types for each of these different things, it can also prevent a whole class of bugs.

Luckily there are libraries that can help with the boilerplate involved in defining those types. Examples are “Foonathan”‘s type_safe library, Boost Strong typedef (which is only part of a library), PhysUnits/quantity and Boost.Units.

The net cost will be some implementation time and a little compilation time (those classes tend to be not very complex), but usually little or no runtime cost (if in doubt, use a profiler!).

Thanks to Björn Fahller aka. “Rollbear” for inspiring me to write this post.