C++ Templates – June 20, 2002
Introduction
Understanding the fundamentals of templates in C++ is something that few users of the language can afford to do without. However, those wishing to explore the more advanced features often find themselves struggling with a lack of clear explanation into exactly how templates are supported by the language. A frustrating problem when working with templates is that some vendors implement a subset of the C++ standard. This column will point out common pitfalls and provide an insight into how to produce fast, reusable, and efficient code using C++ templates.
A testament to the power of templates is that entire libraries have been written as a set of template classes, most notably STL and ATL. The standard C++ library provides a wealth of reusable and flexible classes and algorithms, while ATL is the de facto standard for COM programming in C++. To master these and other template libraries, it is important to have more than a basic understanding of how templates work.
Function Templates
By using function overloading, we can perform the same operation on a variety of different data types. To perform addition on both a pair of integers as well as a pair of doubles, we could use an overloaded function:
int add(const int x, const int y) { return x + y; } double add(const double x, const double y) { return x + y; }
In this case, the compiler will correctly resolve a function call based on the types of its parameters.
// Calls int add(const int, const int); const int z1 = add(3, 2); // Calls double add(const double, const double); const double z2 = add(3.0, 2.0);
If we need to deal with another type, we would have to provide another function overload. While each of the function overloads uses a different data type, they each follow the same pattern, and so in principle the compiler could generate this code for us, whenever we need to call the add function on a particular data type. A template function does exactly this:
template<class T> const T add(const T &t1, const T &t2) { return t1 + t2; }
Conceptually, the compiler recognizes the template through the template keyword, which is followed by the template parameter list, consisting of one or more template parameters. When add is called on a particular type, the compiler will take the template definition and replace any occurrences of the template parameters with the given types. In this example, the template paramter list consists of the single type template parameter T. Using a template function differs from function overloading because the compiler automatically generates code for each new type that is required.
We can use the add template with any type that has an operator + defined. Suppose a custom String class provides string concatenation and knows how to write itself to std::ostream. Because String is compatible with the template function, we can call it to perform string addition:
// Sample strings. const String strBook("book"); const String strWorm("worm"); // Displays "bookworm". cout << add(strBook, strWorm) << endl;
Seeing that we intended to add two String values, the compiler will generate the appropriate add function on our behalf, which would look something like:
const String add(const String &t1, const String &t2) { return t1 + t2; }
Explicit Instantiation
When calling the template function, the compiler will choose the correct type used to instantiate the template. Although the standard allows for explicit template instantiation, not all vendors have correctly implemented it. For example, the 6.0 release of Visual C++ silently calls the wrong function:
template<class T> int getSize(void) { return sizeof(T); } // Outputs 4, should be 8. cout << "double: " << getSize<double>() << endl; // Outputs 4, correctly. cout << "int: " << getSize<int>() << endl;
Designers of cross-platform code may not want to rely on explicit template function instantiation until better compiler support is available.
Class Templates
The analogy of function templates also applies to classes. Templates can be used to provide a family of classes from a generic pattern. If we needed a full complement of arithmetic to supplement the add function, we could consider using a class. If the actual operands changed infrequently relative to the number of function calls, it would make sense to make the two arguments part of the class itself. With templates, it is possible to create a generic class that is parameterized on another type:
template<class T> class CCalculator { public: CCalculator(const T &x, const T &y) : m_x(x), m_y(y){ } ~CCalculator(void){ } const T add(void){ return m_x + m_y; } const T sub(void){ return m_x - m_y; } const T mult(void){ return m_x * m_y; } const T div(void){ return m_x / m_y; } private: const T m_x; const T m_y; };
To instantiate this template class, we need to supply a specific type:
// Create a calculator for integers. CCalculator<int> calc(5, 2); // Should give 10. const int z = calc.mult();
As for function templates, the compiler will create a class for each type that it supplied to the template. This provides a powerful mechanism for code reuse, allowing a single template to act on any compatible data type.
Template Compilation Models
When writing template classes, function definitions are usually stored in a header file along with their declarations, and not in a separate .cpp file. Trying to do otherwise typically will result in a linker error. This is because most compilers require template definitions to be available to each translation unit that uses them, through the inclusion of header files.
The reason for this behaviour is that templates are only a pattern, and as such they do not directly produce code until the compiler comes a across an instantiation. If we create a CCalculator<int> instance, and invoke one of its class methods, the compiler will need to have the function definition at hand. If the header file is included and contains this definition, all is well. But if the definition resides in a .cpp file, the compiler cannot be expected to find the pattern at the time, and use it to generate the required code. However, the C++ standard does provide a mechanism to aid compilers. The export keyword instructs the compiler that we are providing a separately compiled template:
// In MyTemplateFunction.h template<class T> void myTemplateFunction(const T &t1); // In MyTemplateFunction.cpp export template <class T> void myTemplateFunction(const T &t1) { ... }
Today, most compilers require template definitions to be explicitly added to a translation unit through header file inclusion, although the standard does cater to definitions that reside in .cpp files. The two different template compilation models are traditionally known as the inclusion model and the separation model. At the time of writing, the only compiler that I know of which supports the separation model is the Comeau C++ compiler. The Comeau implementation goes some way to support the usage of the export keyword as intended by the standard, but is currently still in beta release.
The typename Keyword
Another keyword related to the use of templates is the typename keyword, which has two uses. Consider the following template class.
template<class T>
void myFunction(void)
{
// This is problematic.
T::x1 * x2;
}
An initial reading might indicate that myFunction declares a pointer x2 of type T::x1. However, the function could also be invoking the binary multiplication operator on a member variable x1 of the class T and a global variable x2. Using the typename keyword instructs the compiler that an unknown identifier is a type:
// T:x1 is a type and x2 is a pointer
typename T:x1* x2;
The second use of the typename keyword is in place of the class keyword when specifying template parameters:
// These two are identical ...
template<class T1, class T2>;
template<typename T1, typename T2>;
The standard allows for the use of either, and the choice is purely stylistic.
Member Function Templates
In addition to global template functions, the language also supports member template functions: A class may have a member function that uses a template parameter list. Consider the following non-template class, in which the constructor has been templated:
class CTypeSize { public: template<class T> CTypeSize(const T &t1) : m_nSize(sizeof(t1)) { } ~CTypeSize(void){ }; int getSize(void) const{ return m_nSize; } private: const int m_nSize; };
When a template member function is called, the compiler will use the template pattern to generate code for the given type. In this case, we can create an instance of CTypeSize by providing an variable of any type:
// Displays 12. CTypeSize t1("Hello World"); cout << t1.getSize() << endl; // Displays 8 on VC++ 6 / win32. CTypeSize t2(7.0); cout << t2.getSize() << endl;
Sometimes member templates are the most effective way to implement a copy constructor. Consider a simple container that holds a single value:
template<class T> class CSingle { public: CSingle(const T &t1) : m_Value(t1) { } ~CSingle(void){ } T m_Value; };
This then leads to the following problem:
// Create an integer container. CSingle<int> x(7); // This needs a copy constructor ... CSingle<double> y(x);
Using member templates, the copy constructor is simple:
template<class S> CSingle(const CSingle<S> &s1) : m_Value(s1.m_Value) { }
When the compiler can convert an instance of type T into an instance of type S, this will work; this is the case because a double can be constructed from an integer.
Conclusion
Templates are a powerful feature of C++ that allow an algorithm to be abstracted from its data types. This article introduced the basics of template definitions and instantiation. It also covered the differences between function, class, and member templates. The next article in the series discusses default arguments and shows how to create templates with non-type parameters.
About the Author
Kais Dukes is currently working with a leading derivatives pricing specialist, where he is helping to build one of the world’s largest fanancial systems that uses .NET technology. He is finishing a Ph.D. in Artificial Intelligence, and is also completing a book on Templates and Generative Programming in C++. A selection of his more popular articles can be found at www.kaisdukes.com. Kais can be reached at [email protected].