Using Templates
In this post: I briefly outline the use of templates in C++ before giving some examples of interesting use-cases and patterns.
Useful background: C++ classes.
Generic programming is a huge asset to have at one's disposal, providing a variety of boons such as reducing code bloat, expressing complex behaviour more concisely. C++ provides generic paradigms by means of templates, which allow for the writing of code with arbitrary typenames. Now, templates are a pretty straightforward technique to get stuck into, but they can be used in more niche ways to create some interesting effects, which are going to be the focal points of this post.
Here, there are two function arguments which are expected to be of the same type, so there is only one template argument T. This function simply returns the larger of the two function arguments using the less-than operator.
Test:
Output:
Note that using the template function exactly resembles a regular function in this case. Namely, we don't have to specify max<int> because the type argument is automatically inferred from the function arguments.
We can also make class templates in much the same way; most often, this is used to create container classes of some arbitrary type (think of std::vector<T>). For example, we could use a simple container class to store any pair of objects (like std::pair).
Here, we've used two type parameters, as both values could be of any type. In order to construct a Pair, we must provide both of these arguments.
Test:
Output:
Finally, C++14 also introduced the ability to make variable templates. This helps to clean up things like numerical constants which may be required in different types.
Test:
Output:
There are two things to note here. The first is that the keyword class is interchangeable with typename in this context, but the latter was introduced specifically as a more appropriate choice of word.
The second is that it's important to appreciate how templates work, in contrast to other languages. C++ performs code specialisation of templates at compile-time. In other words, it will generate a body of the function/class/variable for every type it knows uses that template. So, the size of the generated object file is no different to if you manually defined each overload yourself, but of course using templates makes our source code much more flexible and concise. This also means that the compiler must be aware of every type that intends to use the template, and also implies that we can't hold pointers to uninstantiated template types (we can have something like vector<int> *v, but not vector *v). In a contrasting example, Java performs type erasure on its generics.
With that in mind, let's look at some of the more interesting use-cases of templates which may not be immediately obvious.
Imagine we wanted a class which counts every instantiation. The premise of this is very simple: all we need is a static counter variable belonging to the class which is incremented in the constructor and decremented in the destructor.
Test:
Output:
Perfect. Now let's use this as an interface for a couple of other classes. These derived classes could be anything whatsoever, as long as they should count themselves.
Test:
Output:
Huh. That's not what we were after. The reason that this happens is that there is no such thing as overriding static members or declaring static members as virtual. There's only a single copy of _count, belonging to the Countable base class, which all derived classes access and increment. This could be the desired behaviour: for is-a relationships, for instance both Dog and Cat deriving from Mammal, instantiations of both dogs and cats should be reflected in the total mammal count.
However, what if we're intending to use Countable as an interface standalone to each class using it, more resembling a composition relationship with Countable? What we need to do is somehow ensure that a new copy of _count is instantiated for every class that uses it. Generating multiple versions of a class at compile-time... that sounds like a job for templating! What if we try a bit of hack wherein we make Countable a class template and make every derived class use some unique type argument that doesn't actually matter at all?
Output from same test:
It works! We've instantiated separate copies of Countable<int>::_count and Countable<double>::_count, which are associated solely with A and B, respectively! Of course, it's not exactly clean code to use random type arguments such as these and we need to ensure that each derived class uses a unique argument. So, the natural choice is just the derived class itself. This is the crux of the curiously recurring template pattern.
Here, I even abstracted the pattern into a macro for class definitions of this sort. Macros such as this can be highly useful for crafting powerful APIs for your codebase, but must be handled with respect and caution. Document them thoroughly and be very mindful of their implications.
CRTP is a powerful idiom which provides a variety of features -- this was just one example!
This is a very straightforward example of a factory. The instantiate function merely statically creates an Object instance and returns it by value, so ObjectFactory::instantiate().get() will print 'Object'. Our factory function could allocate an instance dynamically and return a pointer, instead, and this is indeed a design choice that needs to made, including thinking about smart pointers etc. For this example, I've kept things more simple by just dealing with static allocation.
Note: You may think that doing something like Object o = ObjectFactory::instantiate() is, therefore, performing unnecessary copying of the object, and the code does appear this way. However, compilers do perform copy elision, an technique which reduces pointless copying, including return value optimisation.
Currently, there's not exactly much point in this exercise. What would happen, however, if we wanted to construct subclasses of Object? Well, we could create a factory function for each, or simply make a templated factory such as:
Still, we're not really achieving anything of interest here. A true beauty of factories is their ability to extend the ways we can instantiate objects, so let's look at one of the most common cases: instantiating by name alone. For instance, we might need to be able to instantiate game objects from strings found in level data files. C++ doesn't have native reflection capabilities, but we can create a factory that achieves this. Let's say we have a base class Entity representing all game objects. Firstly, we're going to need a factory function for every possible subclass; at this point, we know that templates make this very easy. What we need to do, though, is somehow convert from a string to the correct specialisation of the factory function. We can make EntityFactory hold a map of std::string to a function pointer, with the instantiate function retrieving and calling the correct factory function.
Note: As we're now dealing with subclass polymorphism, we need to return an Entity pointer instead, dynamically allocating them instead of statically. For the purpose of good practice, I'm going ahead with using std::unique_ptr instead of raw pointers.
The factory holds a map of an entity subclass' string representation to a function pointer. The syntax of that pointer should be read as it returning an std::unique_ptr<Entity> and taking no arguments. The instantiate function gets the function associated with the given string and executes it, returning its result (hence the empty brackets at the end). The at method will throw an exception if an unregistered string is given.
The factory functions in question are generated by the the function template registerEntity, which a factory function for the given type into the map with the given string. This is lambda syntax, defining the function 'on-the-fly' in order to avoid needing a separate factory function template. I've intentionally not used std::make_unique in this situation because the pointer needs to be of type Entity, not the derived T, so we would need an inelegant bit of casting.
Note: C++ gives us std::function as a cleaner encapsulation of function pointers. If you wished to use it, it would take the form std::function<std::unique_ptr<Entity> ()>
Test:
Output:
This is obviously imperfect because we need to manually register every entity subclass before using the factory. Ideally, each subclass should register itself into the factory, so we know it's ready to use by the start of the program. Sadly, we can't just call registerEntity() in free space in class header/source. Fortunately, we can declare variables which may have side-effects in their constructors. Let's create another class template, EntityRegistrar, which again exploits the nature of of template specialisation. We'll have one as a static member of each derived class, with itself as the type argument: when the static member is instantiated, it will call the register method.
Again, you can create and document macros which would abstract away the static registrar when defining classes. I personally find this registrar pattern, as I like to call it, a very useful tool, exploiting the nature of both templates and static instantiation.
Useful background: C++ classes.
Generic programming is a huge asset to have at one's disposal, providing a variety of boons such as reducing code bloat, expressing complex behaviour more concisely. C++ provides generic paradigms by means of templates, which allow for the writing of code with arbitrary typenames. Now, templates are a pretty straightforward technique to get stuck into, but they can be used in more niche ways to create some interesting effects, which are going to be the focal points of this post.
Basic Templating
The most straightforward use of generics in C++ are function templates which, as the name implies, are functions (or indeed member/static methods) which act upon generic typenames. Templates are most often noticed through their use of chevron brackets, which are used for the templates' type parameters/arguments. A simple example would be a function acting upon any type with a natural order (i.e. a defined less-than relation), such as retrieving the maximum of two values:template <typename T> T max(T a, T b) { return (a < b) ? b : a; }
Here, there are two function arguments which are expected to be of the same type, so there is only one template argument T. This function simply returns the larger of the two function arguments using the less-than operator.
Test:
std::cout << max(3, 6) << std::endl;
6
Note that using the template function exactly resembles a regular function in this case. Namely, we don't have to specify max<int> because the type argument is automatically inferred from the function arguments.
We can also make class templates in much the same way; most often, this is used to create container classes of some arbitrary type (think of std::vector<T>). For example, we could use a simple container class to store any pair of objects (like std::pair).
template <typename T, typename U> class Pair { public: T first; U second; };
Here, we've used two type parameters, as both values could be of any type. In order to construct a Pair, we must provide both of these arguments.
Test:
Pair<int, char> p = { 5, 'X' }; std::cout << p.first << p.second << std::endl;
5X
Finally, C++14 also introduced the ability to make variable templates. This helps to clean up things like numerical constants which may be required in different types.
template <typename T> constexpr T PI = 3.1415926535897932385;
Test:
std::cout << PI<int> << std::endl; std::cout << std::setprecision(17) << PI<float> << std::endl; std::cout << std::setprecision(17) << PI<double> << std::endl;
3 3.1415927410125732 3.1415926535897931
There are two things to note here. The first is that the keyword class is interchangeable with typename in this context, but the latter was introduced specifically as a more appropriate choice of word.
The second is that it's important to appreciate how templates work, in contrast to other languages. C++ performs code specialisation of templates at compile-time. In other words, it will generate a body of the function/class/variable for every type it knows uses that template. So, the size of the generated object file is no different to if you manually defined each overload yourself, but of course using templates makes our source code much more flexible and concise. This also means that the compiler must be aware of every type that intends to use the template, and also implies that we can't hold pointers to uninstantiated template types (we can have something like vector<int> *v, but not vector *v). In a contrasting example, Java performs type erasure on its generics.
With that in mind, let's look at some of the more interesting use-cases of templates which may not be immediately obvious.
Curiously Recurring Template Pattern (CRTP)
CRTP is a design pattern in which a class inherits from a template class, giving itself as the type argument. This strange use of a recurrence relation does indeed seem curious, so let's look at a real-world example of what this could achieve.Imagine we wanted a class which counts every instantiation. The premise of this is very simple: all we need is a static counter variable belonging to the class which is incremented in the constructor and decremented in the destructor.
class Countable { private: static size_t _count; public: static size_t count() { return _count; } Countable() { _count++; } virtual ~Countable() { _count--; } }; size_t Countable::_count = 0;
Test:
std::cout << Countable::count() << std::endl; Countable c1; Countable c2; std::cout << Countable::count() << std::endl; std::cout << c1.count() << std::endl;
0 2 2
Perfect. Now let's use this as an interface for a couple of other classes. These derived classes could be anything whatsoever, as long as they should count themselves.
class A : public Countable {}; class B: public Countable {};
Test:
A a1; A a2; B b1; std::cout << A::count() << std::endl; std::cout << B::count() << std::endl;
3 3
Huh. That's not what we were after. The reason that this happens is that there is no such thing as overriding static members or declaring static members as virtual. There's only a single copy of _count, belonging to the Countable base class, which all derived classes access and increment. This could be the desired behaviour: for is-a relationships, for instance both Dog and Cat deriving from Mammal, instantiations of both dogs and cats should be reflected in the total mammal count.
However, what if we're intending to use Countable as an interface standalone to each class using it, more resembling a composition relationship with Countable? What we need to do is somehow ensure that a new copy of _count is instantiated for every class that uses it. Generating multiple versions of a class at compile-time... that sounds like a job for templating! What if we try a bit of hack wherein we make Countable a class template and make every derived class use some unique type argument that doesn't actually matter at all?
template <class Derived> class Countable { private: static size_t _count; public: static size_t count() { return _count; } Countable() { _count++; } virtual ~Countable() { _count--; } }; template <class Derived> size_t Countable<Derived>::_count = 0; class A : public Countable<int> {}; class B: public Countable<double> {};
Output from same test:
2 1
It works! We've instantiated separate copies of Countable<int>::_count and Countable<double>::_count, which are associated solely with A and B, respectively! Of course, it's not exactly clean code to use random type arguments such as these and we need to ensure that each derived class uses a unique argument. So, the natural choice is just the derived class itself. This is the crux of the curiously recurring template pattern.
template <class Derived> class Countable { private: static size_t _count; public: static size_t count() { return _count; } Countable() { _count++; } virtual ~Countable() { _count--; } }; template <class Derived> size_t Countable<Derived>::_count = 0; #define COUNTABLE_CLASS(X) class X : public Countable<X> COUNTABLE_CLASS(A) {}; COUNTABLE_CLASS(B) {};
Here, I even abstracted the pattern into a macro for class definitions of this sort. Macros such as this can be highly useful for crafting powerful APIs for your codebase, but must be handled with respect and caution. Document them thoroughly and be very mindful of their implications.
CRTP is a powerful idiom which provides a variety of features -- this was just one example!
Object Factories
In a general sense, a factory is some object designed to create another object(s). In a lot of standard use, we don't often need to create factories because the usual construction mechanisms fulfill everything we need. However, factories become more relevant when polymorphism gets in the way of creating our objects elegantly. The scope of factory patterns is quite large, so let's just take a look at how templates can be used in relation to them.class Object { public: void get() const { std::cout << "Object" << std::endl; } }; class ObjectFactory { public: static Object instantiate() { return Object(); } };
This is a very straightforward example of a factory. The instantiate function merely statically creates an Object instance and returns it by value, so ObjectFactory::instantiate().get() will print 'Object'. Our factory function could allocate an instance dynamically and return a pointer, instead, and this is indeed a design choice that needs to made, including thinking about smart pointers etc. For this example, I've kept things more simple by just dealing with static allocation.
Note: You may think that doing something like Object o = ObjectFactory::instantiate() is, therefore, performing unnecessary copying of the object, and the code does appear this way. However, compilers do perform copy elision, an technique which reduces pointless copying, including return value optimisation.
Currently, there's not exactly much point in this exercise. What would happen, however, if we wanted to construct subclasses of Object? Well, we could create a factory function for each, or simply make a templated factory such as:
class ObjectFactory { public: template <typename T> static T instantiate() { return T(); } };
Still, we're not really achieving anything of interest here. A true beauty of factories is their ability to extend the ways we can instantiate objects, so let's look at one of the most common cases: instantiating by name alone. For instance, we might need to be able to instantiate game objects from strings found in level data files. C++ doesn't have native reflection capabilities, but we can create a factory that achieves this. Let's say we have a base class Entity representing all game objects. Firstly, we're going to need a factory function for every possible subclass; at this point, we know that templates make this very easy. What we need to do, though, is somehow convert from a string to the correct specialisation of the factory function. We can make EntityFactory hold a map of std::string to a function pointer, with the instantiate function retrieving and calling the correct factory function.
Note: As we're now dealing with subclass polymorphism, we need to return an Entity pointer instead, dynamically allocating them instead of statically. For the purpose of good practice, I'm going ahead with using std::unique_ptr instead of raw pointers.
class Entity { public: virtual void get() const { std::cout << "Entity" << std::endl; } }; class DerivedEntity : public Entity { public: virtual void get() const override { std::cout << "DerivedEntity" << std::endl; } }; class EntityFactory { private: static std::unordered_map<std::string, std::unique_ptr<Entity>(*)()> entityMap; public: static std::unique_ptr<Entity> instantiate(std::string name) { return entityMap.at(name)(); } template <typename T> static void registerEntity(std::string name) { entityMap.emplace( name, []() { return std::unique_ptr<Entity>(new T); } ); } }; std::unordered_map<std::string, std::unique_ptr<Entity>(*)()> EntityFactory::entityMap;
The factory holds a map of an entity subclass' string representation to a function pointer. The syntax of that pointer should be read as it returning an std::unique_ptr<Entity> and taking no arguments. The instantiate function gets the function associated with the given string and executes it, returning its result (hence the empty brackets at the end). The at method will throw an exception if an unregistered string is given.
The factory functions in question are generated by the the function template registerEntity, which a factory function for the given type into the map with the given string. This is lambda syntax, defining the function 'on-the-fly' in order to avoid needing a separate factory function template. I've intentionally not used std::make_unique in this situation because the pointer needs to be of type Entity, not the derived T, so we would need an inelegant bit of casting.
Note: C++ gives us std::function as a cleaner encapsulation of function pointers. If you wished to use it, it would take the form std::function<std::unique_ptr<Entity> ()>
Test:
EntityFactory::registerEntity<DerivedEntity>("DerivedEntity"); std::unique_ptr<Entity> e = EntityFactory::instantiate("DerivedEntity"); e->get();
DerivedEntity
This is obviously imperfect because we need to manually register every entity subclass before using the factory. Ideally, each subclass should register itself into the factory, so we know it's ready to use by the start of the program. Sadly, we can't just call registerEntity() in free space in class header/source. Fortunately, we can declare variables which may have side-effects in their constructors. Let's create another class template, EntityRegistrar, which again exploits the nature of of template specialisation. We'll have one as a static member of each derived class, with itself as the type argument: when the static member is instantiated, it will call the register method.
template <typename T> class EntityRegistrar { public: EntityRegistrar(std::string name) { EntityFactory::registerEntity<T>(name); } }; class DerivedEntity : public Entity { private: static EntityRegistrar<DerivedEntity> er; public: virtual void get() const override { std::cout << "DerivedEntity" << std::endl; } }; EntityRegistrar<DerivedEntity> DerivedEntity::er("DerivedEntity");
Again, you can create and document macros which would abstract away the static registrar when defining classes. I personally find this registrar pattern, as I like to call it, a very useful tool, exploiting the nature of both templates and static instantiation.
Comments
Post a Comment