In this article I am going to take you through a brief introduction of templates along with some of the common issues that programmers face. What are templates?Templates are an efficient way of allowing one piece of code to operate on many different types. They are written using unspecified types which are specified at the point of use. No performance overhead is added because templates are instantiated as classes at compile time not run time. Templates are a powerful mechanism that allows a programmer to implement a wide variety of classes and functions from a smaller code base. A common example used to illustrate the use of templates is a mathematical vector class. A vector class should provide storage for the components of the vector (eg. xyz coordinates) as well as functions to perform basic vector calculations. In an application we may have a need for a Vector class for ints in one place and for a Vector class for floats in another place. An object orientated programmer's first instinct might be to derive subclasses named IntVector3 and FloatVector3 from a base class named Vector3. However, inheritance is unnecessary, it demands extra code to replicate the same functionality for both int and float and extra unneeded code presents an opportunity to introduce extra unneeded bugs. Templates provide a much more elegant solution. Here is an example: template <typename T> class Vector3 { private: T X, Y, Z; public: T GetX() const { return X; } T GetY() const { return Y; } T GetZ() const { return Z; } void SetX(const T& value) { X = value; } void SetY(const T& value) { Y = value; } void SetZ(const T& value) { Z = value; } }; Vector3<float> FloatVec; Vector3<int> IntVec; Above the class declaration, you'll find the template declaration 'template <typename T>'. The keyword 'template' begins a template declaration with the template parameters specified inside the triangular brackets. Additional parameters are delimited by commas. In this example, you'll note that there is only one template parameter 'T'. A template parameter has two parts, the type parameter and the identifier. The type parameter defines the identifier to be a typename if declared using the 'class' or 'typename' keywords or a template name if declared using the 'template' keyword. Using the 'typename' or 'class' keywords allows you to pass in any type whether it is a class, struct, enumeration, bool, integral, or floating-point type. The identifier portion of the template parameter gives the parameter its name. In the above example the identifier happens to be 'T', but you can use any valid C++ identifier to name a template parameter. When a Vector template is instanced, each occurrence of the identifier 'T' in the class definition and implementation will be replaced by the type specified in the template declaration. In the case "Vector3<float> FloatVec;", 'T' is replaced with 'float' and in the case "Vector3<int> IntVec;", 'T' is replaced with 'int'. Where "T X, Y, Z" is defined in the class, the statement becomes "float X, Y, Z" and "int X, Y, Z" respectively and so to with the return types and parameters of the accessor functions. A lot more functionality could be added to this Vector class; however, I have kept it simple for the sake of illustration. It is important to remember that Vector3 is not a class but a template. During compilation the compiler uses a template to create a particular classes based on the pattern that the template defines. In this example, Vector3 is not the class, Vector3<int> is the class as is Vector3<float>. The compiler actually generates a new class for both. You can have more than one type in a template. Define the extra types in the template definition statement. For example: template <typename T, typename G> struct MyExampleStruct { T x; G Y; }; MyExampleStruct<int, float> MyObject; In this example, 'T' will be replaced with an int and 'G' with a float. This example uses a struct instead of a class but the template syntax for both is the same. In C++ structs and classes are inherently similar items with the primary distinction that the default permission level of a class is 'private' while it's 'public' for a struct. Another improvement that could be made to this Vector template is to not limit it to three coordinates. For example, at some point in the future you might want to use a Vector with four coordinates. In situations like this passing values can be useful. For example: template <typename T, std::size_t size> class Vector { T values[size]; }; Vector<float, 3> vector3; The size parameter in the template instancing declaration is used within the resulting class to create an array of the specified type and the specified number of elements - in this case, a float array of three elements. Functions can also be made into templates. For example: template <typename T> T foo(T& param) { return param + 1; }; void sample() { int delta; std::cout << foo<int>(delta); }; In this example, the function "foo" uses 'T' as both a parameter type and a return type. In order for the function "sample" to make a call to "foo" a type argument must be supplied to the templated function. In this case, int is substituted for 'T'. Template specialization allows you specify custom functionality for a particular type. For example: template <typename T> T Max(const T value1, const T value2) { return value1 < value2 ? value2 : value1; }; template <> const char * Max(const char *value1, const char *value2) { return (strcmp(value1, value2) < 0) ? value2 : value1; }; The function above returns the maximum value between two values. The generic template t the top will do fine for most situations. We just do a simple comparison using the less than operator and return whichever value is greater. How about C style strings, where the greater than and less than operators have no relevant meaning? With template specialisation we can provide a generic implementation above that will be used for data types other than "const char*" and a specialisation that will be invoked whenever the string is passed in. Templates are quite often used with container classes, containers being a class that hold a series of objects, similar to an array but with a lot more features and safety. Templates are quite suited to this task since they actually promote type safety. In C# and Java, the containers rely on the fact that all objects within those languages are derived from a single base class (normally called Object). These types of containers are called Polymorphic containers. The problem is if you want any sort of type safety you generally have to derive a class from the container class and instruct it to only allow in a certain object type. Since with templates it actually generates a new class/function based on the input type, it has all the type checking mechanism of any other C++ class/function. C++ comes with its own set of containers in the STL (Standard Template Libraries) libraries; examples include std::vector, std::deque, std::map and std::list. It is recommended that you use these containers over writing your own since the C++ designers have gone to great lengths to insure performance and it reduces the bugs you introduce by developing your own containers. That being said C# and Java are both introducing a similar mechanism to templates called Generics to address this issue. |
|