Before we begin...
This article has been reformatted to be more readable on GameDev.net, the original can be found at the following blog
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...
This article provides example issues with template typedefs and the alternatives that modern C++ provides.
Let’s make a game!
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
(Actually, if this gets any more complicated it would be a good idea to take a look at prototype and component patterns at http://gameprogrammingpatterns.com
and save yourself a headache).
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 illegalILLEGAL???
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 typedef
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 where 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;
Notice the 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 Template
In the C++ 11 standard, type alias and alias templates fill this hole in functionality. 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>>;
This feels much cleaner, the same technique can be applied to all the above examples as well.
So here are the details
- 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. The new functionality allowing us to define aliases on templates like we never could before.