Section 2 - Template Instantiation, Parameters and Static
8B.2.1 Template Instantiation
When the compiler generates a class or function from a template, it is referred to as template instantiation.
- A class generated from a class template is called a generated class.
- A function generated from a function template is called a generated function.
The compiler generates a class, a function or a static data member from a template when it sees an implicit instantiation or an explicit instantiation of the template.
8B.2.1.1 Implicit and Explicit Instantiation of Class Templates.
Implicit Instantiation - Class Templates
This is an example of implicit instantiation of a class template.
template <class T> class Z { public: Z() {}; ~Z() {}; void f(){}; void g(){}; }; int main() { Z<int> zIint; // implicit instantiation generates class Z<int> Z<float> zFlloat; // implicit instantiation generates class Z<float> return 0; }
The surprising thing to note here is that the functions f() and g() are not instantiated because they were never used by the client, main(). When a class is instantiated, the constructors and destructors will be instantiated (compiled) but there is no guarantee that other member methods will be. For that, you have to inspect your client and see if it invokes these other methods.
The next example uses the template class members defined above, Z<T>::f() and Z<T>::g() and it does result in more actual functions being compiled as a result of implicit instantiation.
template <class T> class Z { public: Z() {}; ~Z() {}; void f(){}; void g(){}; }; int main() { Z<int> zIint; // implicit instantiation generates class Z<int> zIint.f(); // and generates function Z<int>::f() Z<float> zFloat; //implicit instantiation generates class Z<float> zFloat.g(); //and generates function Z<float>::g() return 0; }
This time in addition to the generating classes Z<int> and Z<float>, with constructors and destructors, the compiler also generates definitions for Z<int>::f() and Z<float>::g(). The compiler does not generate definitions for functions, non-virtual member functions, classes or member classes that do not require instantiation. In this example, the compiler did not generate any definitions for Z<int>::g() and Z<float>::f(), since they were not required.
Explicit Instantiation - Class Templates
Let's look at an example of explicit instantiation of a class template. Despite the lack of instantiation of Z bound to some type like int or float in main(), the class will nevertheless be instantiated (compiled) due to the explicit instantiation syntax.
template <class T> class Z { public: Z() {}; ~Z() {}; void f(){}; void g(){}; }; int main() { template class Z<int>; // explicit instantiation of class Z<int> template class Z<float>; // explicit instantiation of class Z<float> return 0; }
Pointers With Class Templates
Consider the following sample. Will the compiler generate any classes in this case?
template <class T> class Z { public: Z() {}; ~Z() {}; void f(){}; void g(){}; }; int main() { Z<int>* pZint; Z<float>* pZfloat; return 0; }
This time the compiler does not generate any class definitions because there is no need. It is similar to declaring a pointer to an undefined class or struct.
8B.2.1.2 Implicit and Explicit Instantiation of Function Templates.
Implicit Instantiation - Function Templates
Consider the following sample. This is an example of implicit instantiation of a function template.
template <class T> T max(T a, T b) { return a > b ? a : b; } void main() { int i; i = max(10, 15); // implicit instantiation of max(int, int) char c; c = max('k', 's'); // implicit instantiation of max(char, char) }
In this case the compiler generates functions max(int, int) and max(char, char). The compiler generates definitions using the template function max().
Explicit Instantiation - Function Templates
Consider the following sample. This is an example of explicit instantiation of a function template.
template <class T> void test(T rT) { } template void test<int>(int); // explicit instantiation of Test(int) int main() { // Even if no invocations such as Test(5), the function is compiled from above return 0; }
8B.2.2 Template Parameters
Templates, as we saw, allow one to implement a generic Queue<T> template that has a type parameter T. T can be replaced with actual type -- for example, Galaxy -- and C++ will generate the class Queue<Galaxy>. T is called a template parameter, also referred to as type-parameter.
C++ allows you to specify a default template parameter, so the definition could now look like:
template <class T = float, int elements = 100> Stack { ....};
Then a declaration such as
Stack<> mostRecentSalesFigures;
would instantiate (at compile time) a 100 element Stack template class named mostRecentSalesFigures of float values; this template class would be of type Stack<float, 100>.
Notice that in the parameter list, we have allowed an int parameter in addition to a class parameter. This allows us to require (or offer) more than just the class name in the instantiation. For one of the Stacks as defined above, we are allowed to include the size of the Stack in the instantiation. Stack<Galaxy, 250> becomes a valid instantiation, as does Stack<Galaxy> or just Stack<>.
Another way to express this last idea is that C++ allows non-type template parameters. In this case, template class Stack has an int as a non-type parameter.
If you specify a default template parameter for any formal parameter, all subsequent parameters must have defaults. This is the same requirement as we have with default parameters in C++ functions.
A type-parameter (or class parameter) defines its identifier to be a type-name in the scope of the template declaration and cannot be re-declared within its scope (including nested scopes). For example,
template <class T, int size> class Stack { int T; // error type-parameter re-defined. void f() { char T; // error type-parameter re-defined. } };
This contrasts what we know about normal variable and parameter names where we can reuse an identifier name at a nested scope level if we wish to (or accept that it will) "hide" the higher level use of the same identifier.
Also, the value of a non-type-parameter cannot have its value changed or assigned. For example,
template <class T, int size> class Stack { void f() { size++; // error change of template argument value } };
A non-type template parameter cannot be of floating type. For example,
template <double d> class X; // error undefined class 'X<1.e66>' //template <double* pd> class X; // ok //template <double& rd> class X; // ok
Remember that you may not get the error message until and unless you attempt to instantiate a template. Some errors don't surface if the template is defined but not instantiated explicitly or implicitly.
8B.2.3 Static Members and Specialization
Each template class or function generated from a template has its own copies of any variables or members declared static in the template. These copies may not only be different, but even be of different types if they are defined in terms of the template parameter, T. For example,
template <class T> class X { public: static T s; }; int main() { X<int> xi; X<char*> xc; }
Here X<int> has a static data member s of type int and X<char*> has a distinct static data member s of type char*.
Static class members come about when we declare any member of a template to be static (even if the type is not defined in terms of the template parameter, T):
#include <iostream> using namespace std; template <class T> class X { public: static int s; }; template <class T> int X<T>::s = 0; // notation for static initialization in a template int main() { X<int> xInt1, xInt2; // two objects of the same int class X<float> xFloat; // an object of the float class, different from above xInt1.s = 31; xInt2.s = 32; xFloat.s = -12; // the two X<int> objects share the same static cout << "xInt1.s = " << xInt1.s << endl; cout << "xInt2.s = " << xInt2.s << endl; // but the X<float> is a different class so it has a different static cout << "xFloat.s = " << xFloat.s << endl; return 0; } /* Same run --------------------- xInt1.s = 32 xInt2.s = 32 xFloat.s = -12 Press any key to continue . . . --------------------------------- */
If it so happens that the static member is of type T, then we have a "concept" in the initialization: X<T>::s = 0. This would cause a compiler -- or possibly a run-time -- error, if T is string or char*. The solution is to allow each class instance to override the static initialization. This is done with the "specialization" notation in the following example
#include <iostream> #include <string> using namespace std; template <class T> class X { public: static T s; }; template <class T> T X<T>::s = 0; // generic fall-back initialization of static in template template <> int X<int>::s = 3; // override (specialization) for T = int template <> string X<string>::s = "Hello"; // override for T = string int main() { X<int> xInt; X<string> xString; // the two X<int> objects share the same static cout << "xInt.s = " << xInt.s << endl; // but the X<float> is a different class so it has a different static cout << "xString.s = " << xString.s << endl; return 0; } /* Sample run--------------------- xInt.s = 3 xString.s = Hello Press any key to continue . . . ---------------------------- */
The same holds for static variables in template functions. The static local variables in different versions of these functions do not interact.