C++ is more than a mere object-oriented programming language. The full power of C++ is seen when programming with templates. Templates lie at the heart of the standard library: strings, I/O streams, containers, iterators, algorithms, and more.
A template is a pattern for creating classes or functions as instances of the template at compile time, similar to the manner in which a class is a pattern for creating objects as instances of the class at runtime. A template takes one or more parameters, and when you instantiate a class or function template, you must supply arguments for the parameters. The classes and functions can have different behaviors or implementations, depending on the arguments. This style of programming is often called generic programming. Templates can also be used to select code at compile time, affect the behavior of generated code, and set policies. This style of programming is often known as template metaprogramming.
Programming with templates is unlike traditional object-oriented programming. Object-oriented programming centers around type polymorphism (requiring classes, objects, and virtual functions). Template-based programming centers around parametric polymorphism, in which a function or class is defined independently of its parameters (which can be values, types, or even other templates).
This chapter describes the syntax and semantics of declaring, specializing, instantiating, and using templates. See Chapter 8 for information about some typical uses of templates in the standard library. See Appendix B for information about template-oriented projects.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
A template declaration can be a function declaration, function definition, class declaration, or class definition. The template declaration takes one or more parameters, which can be values, types, or class templates.
In most cases, you can use a template simply by naming the template and providing arguments for the template parameters: constant expressions, types, or template references. This is known as instantiating the template. You can instantiate a template at its point of use, or declare a separate instantiation as a class or function declaration. An instance of a function template creates a function; an instance of a class template creates a class and all of its members.
A template lets you define a class or function once for a wide range of template arguments, but sometimes you need to customize a template for particular arguments. This is known as specializing the template. A template specialization, as its name implies, is a special case of the template pattern. When you instantiate a template, the compiler uses the template arguments to pick a specialization, or if no specialization matches the arguments, the original template declaration. A specialization can be total or partial. A total specialization specifies values for all of the template arguments, and a partial specialization specifies only some of the template arguments.
The terminology used in this book reflects the terminology that many C++ programmers have adopted, even though that terminology differs slightly from that used in the C++ standard. In the standard, "specialization" means an instance of a template. "Instantiation" also refers to an instance of a template. When you declare a special case of a template, that is known as explicit specialization.
Many C++ programmers prefer to keep specialization and instantiation as separate concepts and separate terms, and I have adopted the simpler terminology for this book.
Note that a template declaration defines only the pattern. A specialization defines a pattern that applies to a specific set of template arguments. Only by instantiating a template do you declare or define a function or class. When you instantiate a template, the compiler uses the template arguments to pick which pattern to instantiate: a specialization or the main template.
Writing a template is more difficult than writing a non-template class or function. The template can be instantiated in almost any context, and the context can affect how the template definition is interpreted. Name lookup rules are more complicated for templates than for non-templates.
Example 7-1 shows several different kinds of templates and their uses.
Example 7-1. Declaring and using templates
#include <cmath> #include <complex> #include <iostream> #include <ostream> // Template declaration of pointtemplate<typename T> class point {
public: typedef T value_type; point(const T& x, const T& y) : x_(x), y_(y) {} point() : x_(T( )), y_(T( )) {} T x( ) const { return x_; } T& x( ) { return x_; } void x(const T& new_x) { x_ = new_x; } T y( ) const { return y_; } T& y( ) { return y_; } void y(const T& new_y) { y_ = new_y; } private: T x_, y_; }; // Instantiate point<>.typedef point<std::complex<double> > strange;
strange s(std::complex<double>(1, 2), std::complex<double>(3, 4)); // Specialize point<int> to use call-by-value instead of const references.template<> class point<int> {
public: typedef int value_type; point(int x, int y) : x_(x), y_(y) {} point( ) : x_(0), y_(0) {} int x( ) const { return x_; } int& x( ) { return x_; } void x(int new_x) { x_ = new_x; } int y( ) const { return y_; } int& y( ) { return y_; } void y(int new_y) { y_ = new_y; } private: int x_, y_; }; // Instance of the specialized point<int>point<int> p(42, 0);
// Instance of the general point<>, using long as the template argumentpoint<long> p(42, 0);
// Function templatetemplate<typename T> T abs(T x)
{ return x < 0 ? -x : x; } namespace { // Explicit instantiationconst int abs_char_min1 = abs<int>(CHAR_MIN);
// Implicit instantiationconst int abs_char_min2 = abs(CHAR_MIN);
} // Overload abs( ) with another function template.template<typename floatT, typename T> floatT abs(const point<T>& p)
{ return std::sqrt(static_cast<floatT>(p.x( )*p.x( ) + p.y( )*p.y( ))); } int main( ) {point<double> p;
// Call instance of function template. Compiler deduces second template // argument (double) from the type of p.double x = abs<long double>(p);
std::cout << x << '\n'; // prints 0 std::cout << abs_char_min1 << '\n'; }