Before we begin…
All the source I’ll refer to is up on GitHub, play around with it, break it and let me know what you think. I’m doing this to get better at what I do, and hopefully help people at the same time.
Are you sitting comfortably?
C++ supports two powerful abstractions, Object Orientation and Generic Programming. Ask any battle-hardened games industry veterans about the two and you’re likely to see an eye twitch with the latter. It’s not that Generic Programming is particularly hard but the errors you get out of the language can be particularly verbose without even getting to the private hell of errors relating solely to that usage…
But there’s hope. Our language is evolving. C++ 11 brought some incredible new functionality… Type Alias and Alias Templates and if we’d had them earlier the work I did on a title would have taken much less time and been infinitely more readable.
Motivating Example Let’s make a game!
This topic is dry enough without making it wear a dinner jacket. Games are fun, games need the best out of these techniques, so let’s not make learning them a chore.
Let’s say you have a simple game where multiple wizards lay the smack down, nerdy-spellcast style! We’ll impose some rules:
- Each battle arena contains several magic pools, each imbued with a different spell.
- A wizard casts spells using these pools (maybe their robes soak up the juice?)
- Over time, these pools lose their power. When the power is lost, spells can no longer be cast.
Sounds… fun? Let’s get into it.
Let’s briefly look at two approaches to the modelling of spells. Often a key difference between OO and Generic code is that we may have a reliance on dispatch when identifying “IS-A” relationships with the former, and Type Traits or Duck Typing for the latter.
Your OO code may look something like:
class ISpell abstract
With the concrete specification of two spells:
class MagicMissileSpell : public ISpell
class HealSpell : public ISpell
Your Generic approach on the other hand is likely to be more like:
With the implementation provided by individual classes satisfying whatever functionality the spell requires:
There are benefits and pitfalls to both approaches and in all honesty the two aren’t even mutually exclusive. Let’s not dwell on exactly why you would pick one implementation over the other (I didn’t) but instead focus on how to make the code work well (I had to).
It seems like we need some consideration over ownership in this game:
“Over time, these pools lose their power. When the power is lost, spells can no longer be cast.”
Ownership semantics in C++ 11 are supported in one way with smart pointers. We can model this scenario by letting each pool hold a shared pointer to the spell type, with each wizard holding a weak pointer to the same asset as required. As long as we lock that weak pointer whilst we cast, the condition should be fine (we do have a small amount of time where the cast could be using magic no longer in the pool, but we’ll pretend Wizards are just down with that).
OO Spell Ownership
This looks pretty easy, we’ll set up:
bool cast(std::weak_ptr<ISpell> spell);
We could choose to define a type for these pointers making them easily alterable and reducing the amount of typing:
typedef std::shared_ptr<ISpell> SharedSpellPtr;
typedef std::weak_ptr<ISpell> WeakSpellPtr;
Looks OK, we’re actually going to leave the OO approach now as it doesn’t suffer from the same plague affecting the Generic approach but feel free to check out the source for a more in-depth comparison.
Generic Spell Ownership
Let’s take a quick step back and look at how our spells are modelled again. The pools in this implementation will want to be imbued in a similar way, so how would that look? As we don’t have the common base we will have to bind to a template on the pool:
//... will have mSpell variable, related to T
An explicit specialisation of T can be provided as a constructor argument. For example, a magic missile spell:
In the same manner as the OO aproach, we can probably define this as a custom type:
typedef Spell<MagicMissile> MagicMissileSpell;
Maybe even go further…
typedef std::shared_ptr<MagicMissileSpell> MagicMissileSpellPtr;
This is especially useful if we were overriding types with allocators etc as we get to avoid writing an essay every time we use the type (which would also be error prone as hell).
The problem here is that we’re going to have to jump through the same hoops to define the weak pointer, and any other structures we wanted further down the line (unique pointers, vectors, maps…). It doesn’t scale too well and needs a lot of boilerplate for every spell.
Wouldn’t it be great if we could define a more abstract template type for the above? We can… eventually. Let’s start with a more general shared spell shared pointer:
typedef std::shared_ptr<Spell<T>> SpellSharedPtr;
This looks innocuous enough… but try to compile and *gasp*
error C2823: a typedef template is illegal
ILLEGAL??? That’s not ideal… and sure enough, this is a well trodden restriction of olden times C++.
The common workaround is to take advantage of the fact that classes can be templated, and can contain
template < typename T >
typedef std::weak_ptr< T > SpellWeakPtr;
typedef std::shared_ptr< T > SpellSharedPtr;
typedef SpellType<MagicMissile> MagicMissileSpellType;
Which now means that we can refer to the various pointers like so:
This is the point a lot of literature leaves the subject. Sadly it can still get a little worse. Disappointment comes whenever we want to use that type definition (e.g. if we set up a magic pool like so):
class Pool final
explicit Pool(SpellType<T>::SpellSharedPtr spellPtr)
On compilation of the above, we’re again greeted with a nice compilation error:
warning C4346: ‘SpellType<T>::SpellSharedPtr’ : dependent name is not a type. prefix with ‘typename’ to indicate a type
This one is pretty obviously fixable, we just need to rephrase that declaration every time we see it:
We’ve got a workable solution, there’s one last consideration here though…
What if our spells were referenced in a large amount of places? Maybe we’re not so sure whether the pool should be the sole owner anymore, shared ownership might be fine but the model holds well together… for now. Let’s define an alias (remember that name for later). We reserve the right to change type later and it’s going to be a single point of change (with some hopefully minor fiddling with locks etc, dependant on functionality):
typedef typename SpellType<T>::SpellSharedPtr Type;
typename again, you’ll probably forget to type it every time. There was a point where every code review I ever took for this pattern had someone arguing against that keyword too. The technique works well enough but when you’ve had to defend your code for the fiftieth time, you really wish there was an alternative…
Type Alias, Alias Templates
In the C++ 11 standard, type alias and alias templates comes to save the day. For Visual Studio this means upgrading to 2013 but it’s worth the wait.
Remember when we couldn’t even define this type:
typedef std::shared_ptr<Spell<T>> SpellSharedPtr;
The syntax for Alias Templates make this all possible by propagating the template binding:
using SpellSharedPtr = std::shared_ptr<Spell<T>>;
For me, the incredible part about this is that we no longer need to define an indirect class, we also no longer need to pepper
typename everywhere and it Just Works™. It’s awesome and I’m so glad it exists!!!
I’ll not bore you with how this translates to the above as it should be pretty obvious but again, check out the source to see if you’re interested.
- A type alias declaration introduces a name which can be used as a synonym. This is essentially the new typedef.
- An alias template is a template which allows substitution of the template arguments from the alias template. This is new functionality that allows us get out of our world of pain.
There are no downsides wholeheartedly adopting Alias instead of
typedef everywhere as far as I can tell. The source is all up on GitHub where you can see how this pyjama’d battle royale ends, I’ve written all three approaches separately. Let me know what you think and whether you think it’s right and proper for me to deprecate
typedef in everything I write from now onwards… I’m pretty sold and I think I do.
These guys know their stuff
Some of the stuff I’ve been reading/watching around this time, either helping me form conclusions or just inspiring me to write something: