Overview
Overview
Understanding C++ Templates In this article, we’ll learn the basic syntax and semantics of the features in the templates in C++ and write small programs.
T
his article is a follow-up to the ‘An Introduction to C++ Templates’ article published in the October issue of LFY. So, without further ado, let’s get
started.
Class and function templates There are two kinds of templates: function templates and class templates. To define a template, use the template keyword with the template parameters given within ankle brackets, and provide the class or function definition after that. For example: // array_container is a ‘class template’ template
class array_container { T arr[10]; // other members }; // generic swap is a ‘function template’ template void swap(Type &t1, Type &t2) { Type temp = t1; t1 = t2; t2 = temp; }
The conventional names typically used for template-type arguments are T and Type, but it can be any valid variable name.
Template instantiation We can ‘use’ the template by providing necessary arguments, which is known as instantiating the template. An instance created from that template is a template instantiation. For class templates, we have to explicitly provide the instantiation arguments. For function templates, we can explicitly provide the template arguments or else the
88
NOVEMBER 2007
|
LINUX FOR YOU
|
www.linuxforu.com
CMYK
Overview
compiler will automatically infer the template arguments from the function arguments. For example, here is how we can instantiate the array_container and swap templates:
T arr[size]; public: int getLength() { return size; }
// type arguments are explicitly provided
// other members
array_container arr_int;
};
float i = 10.0, j = 20.0;
array_container arr_flt;
// compiler infers the type of argument as float and
cout<<“array size is “ << arr_flt.getLength();
// implicitly instantiates swap(float &, float &);
// prints: array size is 10
swap(i, j); // or explicitly provide template arguments like this:
Implicit instantiation and type inference
// swap(i, j); cout<<“after swap : “<< i << “ “ << j; // prints after swap : 20 and 10
Template parameters Template parameters are of three types: type parameters, non-type parameters and template-template parameters. Type parameters are used for abstracting the type details in data-structures and algorithms. Non-type parameters are used for abstracting the values known at compile-time itself. A template itself can be passed as a parameter to another template, which is a template-template parameter. We’ve already seen an example for type parameters, and we’ll cover non-type parameters in the next section. Template-template parameters are not covered in this article as it’s an advanced topic. Type parameters can be declared with class or typename keywords and both are equivalent. Using the keyword class for a template type parameter can be a little confusing, since the keyword class is mainly used for declaring a class; so it is preferable to use the typename keyword for type parameters. For example:
The implicit inference of function templates is very convenient to use; the users of the template need not even know that it is a template (well, in most cases), and use it as if it were an ordinary function. This is a powerful feature, particularly when used with function overloading. We’ll look at a simple example for inference of template parameters here: // the types T and SIZE are implicitly inferred from the // use of this function template template int infer_size(T (*arr)[SIZE]) { return SIZE; } int main() { int int_arr[] = {0, 1} ; char literal[] = “literal”; cout<<“length of int_arr: “<
// instead of template use
//
length of int_arr: 2
template
//
length of literal: 8
class array_container;
Template non-type parameters Non-type parameters are used for abstracting constant values in a template class. Non-type parameters cannot be of floating-point type. Also, the value of a non-type parameter should be known at the compile-time itself as templates are a compile-time mechanism and template instantiation is done by the compiler. In the array_container template we saw earlier, we abstracted the type of the array and hard-coded the size of the array. It will be flexible if we can change the size of the array for different uses. Non-type parameters come handy for purposes like this: template class array_container {
In this program, the type and non-type parameters are automatically inferred from the instantiation of infer_size. The first instantiation is for the integer array of Size 2. The T in infer_size is inferred as int and SIZE as 2. Similarly, for the string literal, it becomes char and 8.
Template specialisation A template is to provide the description for the generic implementation of a class or a function. For specific types (or values, in case of non-type parameters), the template is instantiated or specialised. For class and function templates, specialisation results in class and function definitions. Such generated class and function definitions can be used just like any other class or function definitions (they are ‘first class citizens’). If you want to provide a specific implementation for particular types (or values in
www.linuxforu.com
CMYK
|
LINUX FOR YOU
|
NOVEMBER 2007
89
Overview
case of non-type arguments), you can explicitly provide a specialised implementation; this is known as explicit specialisation. In case an instantiation is needed for those particular types or values, instead of creating a class or function definition from the template, if any of the explicit specialisations match, it will be used. Consider that the user-defined class my_vector has a member swap that provides an efficient implementation for swapping two my_vectors. If a swap function with two arguments passing my_vector instances are passed, we prefer to call my_vector::swap instead, using the code in the general swap template. Here is how we can do it: // generic swap is a ‘function template’ template void swap(Type &t1, Type &t2) { Type temp = t1; t1 = t2; t2 = temp; } // explicit specialization for swapping my_vector’s template <> void swap(my_vector &t1, my_vector &t2) { // call my_vector::swap for swapping two my_vector’s
90
NOVEMBER 2007
|
LINUX FOR YOU
|
t1.swap(t2); } int main() { int i = 20, j = 10; // compiler instantiates swap(int &, int &) swap(i, j); my_vector v1(20), v2(10); // however, for my_vector, the specialization is used swap(v1, v2); }
When the explicit specialisation has one or more parameters that still depend on the type or non-type parameters of the primary template, it is referred to as partial specialisation. C++ templates is a very powerful feature, but it is also complex and it takes time to learn the features and master it. In this article, we had a brief overview of the features of the templates in C++, which is a good starting point for exploring it further. By: S.G. Ganesh is a research engineer in Siemens (Corporate Technology). He has authored a book “Deep C” (ISBN 81-7656-501-6). You can reach him at [email protected].
www.linuxforu.com
CMYK