Skip to main content

Static Polymorphism

CPP09 W1 M1: Static Polymorphism: Templates


Learning Objectives

At the end of this Module, you will be able to:
  • Explain how to achieve static polymorphic behavior through the use of templates
  • Explain how to write generic code using templates
  • Explain how to declare and implement function templates
  • Explain how to declare and implement class templates
  • Explain how to declare and implement class member function templates

Static Polymorphism


Introduction

Static polymorphic behavior is realized at compile time. A class template acts as a generic class definition from which new class types can be defined by a simple change of parameter types. Whereas a normal class is used to create objects, a class template is used to define new class types, from which objects are then created. The compiler uses the types you supply, combined with the class template, to create a wholly new class type.
Understanding how to declare, implement, and use class and function templates will pay big dividends in many ways. Primarily, it provides you with a mechanism to write generic code. Learning to write generic code can potentially save you a lot of work.

Definition of Template

A template defines a related set of classes or functions. The related set of classes or functions defined by a template share the same code structure and functionality. The class or function template can then be used to declare a new class or function type.
Function Templates
A function template is a generic function declaration and definition from which different versions of the function can be created by the compiler based on the argument types used to call the function. If you think this sounds a lot like overloaded functions you are right. Function templates and overloaded functions are related as you will soon see.
Class Templates
A class template is a generic class declaration and definition from which different, but related, class types can be created by the compiler based on type parameters.
Structure Templates
A structure template is like a class template but using structures instead.

How Templates Work: An Analogy

When you declare and define a template you are creating a generic version of whatever piece of code you are writing, be it a function or a class. In the declaration and definition of the template you will use one or more identifiers as type placeholders. These type placeholders are similar in function to the placeholders in a form letter generated with a word processor.
The below figure illustrates a simple mail merge operation. A master letter is created with placeholders for certain data elements. The structure of the data source is mapped to the master letter by the placeholder identifiers name and age. When the mail merge function is executed, the data source is merged with the master letter to yield the finished letters. The master letter is a generic document that can be reused to generate many specific letter instances. Class and function templates work in similar fashion. A generic function or class is declared and defined. Placeholders are inserted into the code to reserve spots for actual data types. When specific versions of a template function are required the type substitutions are made based on the types of the arguments used to call the function. In the case of template classes, a special syntax is used when a new template class is declared.
mailmerge_template.jpg

Declaring And Using Function Templates

Up until now, if you wanted to create different versions of the same function to operate on different data types you would overload the function. For instance, if you wanted to declare a function named Sum() that took two arguments, added them together, and returned the result you could create several versions of the function like so:
int Sum(int val1, int val2);

float Sum(float val1, float val2);

char Sum(char val1, char val2);
These three functions can be replaced with one function template.
  1. #ifndef SUM_TEMPLATE_H
  2. #define SUM_TEMPLATE_H
  3.  
  4. template<class T> T Sum(T val1, T val2){
  5.  return val1 + val2;
  6. }
  7. #endif
The Sum() function is declared to be a template by the keyword template appearing on line 4. Following the keyword template in angle brackets is the keyword class followed by a placeholder identifier named T. The class keyword as it is used here essentially means “any type”. The placeholder T will then appear somewhere in the function. It can appear in more than one place, as it does here in the parameter list. You can use any valid identifier as a placeholder name, not just T. You can also declare more than one placeholder.
To use the Sum() function template The Sum() function is called the same way as normal functions are called. The example below shows a main() function using the Sum() function on different data types.
  1. #include 
  2. #ifndef SUM_TEMPLATE_H
  3. #define SUM_TEMPLATE_H
  4.  
  5. template<class T> T Sum(T val1, T val2){
  6.  return val1 + val2;
  7. }
  8. #endif
  9.  
  10. using namespace std;
  11. int main(){
  12.  cout<<Sum(3, 25)<<endl;//Integer
  13.  cout<<Sum(3.456, 5.786)<<endl; //Float
  14.  cout<<Sum('a', 'b')<<endl; //Char
  15. //cout<
  16. return 0;
  17. }

Using Multiple Placeholders

The Sum() function template declared and defined in the above example used one type placeholder named T to reserve type spots in the function. Because both of the Sum() function’s parameters are reserved with the same placeholder they must be of the same type when the function is called. To illustrate, let us see what happens when the Sum() function is called with an integer and a float argument as shown in the following line of code:
cout<
Error : in function int main() line no 15: no matching function for call to `Sum(int, double)
When the Sum() function template is called with two different argument types an error results. This error was produced using the Dev C++ 4.9.9.2. One way to eliminate this error is to declare the Sum() function template to use two different placeholders.
  1. #include 
  2. #ifndef SUM_TEMPLATE_H
  3. #define SUM_TEMPLATE_H
  4. template<class T, class U> T Sum(T val1, U val2){
  5.   return val1 + val2;
  6.  }
  7. #endif
  8.  
  9. using namespace std;
  10. int main(){
  11.  cout<<Sum(3, 25)<<endl;
  12.  cout<<Sum(3.456, 5.786)<<endl;
  13.  cout<<Sum('a', 'b')<<endl;
  14.  cout<<Sum(3, 3.5)<<endl;
  15.  return 0;
  16. }
Notice now there are two type placeholders declared on line 5 of the above example. T and U. The U placeholder is used for the second parameter in the Sum() function while the T placeholder is used for the first parameter and the return value. This will solve one problem but introduce another. The above Example shows the revised Sum() function template in use.
Refer to line 14 of the above example. Notice now that the Sum() function is called with the first argument an integer and the second argument a float. By using two placeholders in the function template the error produced by using two different argument types is eliminated. However, the result type of the Sum() function is dictated by the first argument type. Since the T placeholder is used to reserve the type spot for both the first parameter and the return type of the function, whatever type the first argument to the function happens to be will also be the return type of the function. In this example it is an integer. So, the result of calling the Sum() function with the arguments 3 and 3.5 is 6, not 6.5! Notice what happens when the order of the arguments are swapped.

To resolve the ambiguous return type issue simply declare yet another type place holder used specifically to dictate the function’s return type. The below example gives the revised Sum() function with the extra type placeholder V declared and used to reserve the return type.
  1. #include 
  2. #ifndef SUM_TEMPLATE_H
  3. #define SUM_TEMPLATE_H
  4. template<class T, class U, class V> V Sum(T val1, U val2){
  5.  return val1 + val2;
  6. }
  7. #endif
  8.  
  9. using namespace std;
  10. int main(){
  11.  cout<<Sum<int, int, int>(3, 25)<<endl;
  12.  cout<<Sum<double, double, double>(3.456, 5.786)<<endl;
  13.  cout<<Sum<char, char, char>('a', 'b')<<endl;
  14.  cout<<Sum<double, int, double>(3.5, 3)<<endl;
  15. return 0;
  16. }
Notice the Sum() function call on line 11. The function name Sum is followed by a series of type names enclosed in angle brackets. This is referred to as a template specialization. The order of type names appearing in the specialization map to the same order as the function template type parameters. On line 11 the Sum() function is being specialized to take two integers as arguments to the function and return an integer value. Following the specialization is the argument list appearing in parentheses as usual. Line 12 introduces another Sum() function specialization, as does line 13 and line 14. The specialization on line 14 says that the Sum() function will take a double as the first argument, an integer as the second argument, and return a double value.

Declaring And Using Class Templates

Class templates are used to declare and define a generic class structure. The compiler uses the class template and any types supplied via specialization to build a new class type. Let us begin the discussion of class templates with a simple Foo example. Example below shows the declaration and definition of a class template named Foo.
  1. #include 
  2.  
  3. #ifndef FOOTEMPLATEDEF_H
  4. #define FOOTEMPLATEDEF_H
  5.  
  6. template<class T> class Foo{
  7. public:
  8.  Foo(T _val);
  9.  virtual ~Foo();
  10.  void setVal(T _val);
  11.  T getVal();
  12. private:
  13.  T val;
  14. };
  15.  
  16. template<class T> Foo<T>::Foo(T _val):val(_val){}
  17.  
  18. template<class T> Foo<T>::~Foo(){}
  19.  
  20. template<class T> void Foo<T>::setVal(T _val){
  21.  val = _val;
  22. }
  23.  
  24. template<class T> T Foo<T>::getVal(){
  25.  return val;
  26. }
  27.  
  28. #endif
  29.  
  30. using namespace std;
  31.  
  32. int main(){
  33.  Foo<int> f1(1);
  34.  Foo<char> f2('d');
  35.  cout<<f1.getVal()<<endl;
  36.  cout<<f2.getVal()<<endl;
  37.  return 0;
  38. }
The declaration of the Foo class templates begins on line 6 with the keyword template. There is only one template parameter declared named T. The T is used throughout the class declaration and definition to reserve a spot for the type declared when the Foo class is specialized.
Referring to line 33 of above example, notice how the Foo class template is specialized to use an int type. The important point to note in this example is that Foo and Foo are two distinct types.

A More Complex Class Template Example

The below Example gives the source code for a template of the DynamicArray class.
  1. #include 
  2.  
  3. #ifndef _DYNAMIC_ARRAY_H
  4. #define _DYNAMIC_ARRAY_H
  5.  
  6. template<class T> class DynamicArray{
  7. public:
  8.  DynamicArray(int _size = 5);
  9.  virtual ~DynamicArray();
  10.  T& operator[](unsigned i);
  11.  int getSize();
  12. private:
  13.  T* its_array;
  14.  int size;
  15. };
  16.  
  17. template<class T> DynamicArray<T>::DynamicArray(int _size):size(_size){
  18.  its_array = new T[_size];
  19.  for(int i=0; i<size; i++)
  20.   its_array[i] = static_cast<T>(0);
  21. }
  22.  
  23. template<class T> DynamicArray<T>::~DynamicArray(){
  24.  delete[] its_array;
  25. }
  26.  
  27. template<class T> T& DynamicArray<T>::operator[](unsigned i){
  28. if(i >= (size)){
  29.  int newsize = size+10;
  30.  T* temp = new T[size];
  31.  for(int j = 0; j<size; j++){
  32.  temp[j] = its_array[j];
  33.  }
  34.  delete[] its_array;
  35.  its_array = new T[newsize];
  36.  for(int j = 0; j<size; j++){
  37.   its_array[j] = temp[j];
  38.  }
  39.  for(int j=size; j<newsize; j++){
  40.   its_array[j] = static_cast<T>(0);
  41.  }
  42.  delete[] temp;
  43.  size = newsize;
  44.  return its_array[i];
  45. } 
  46. else 
  47.  return its_array[i];
  48. }
  49.  
  50. template<class T> int DynamicArray<T>::getSize(){ return size;}
  51. #endif
  52.  
  53. using namespace std;
  54.  
  55. int main(){
  56.  DynamicArray<char> d1;
  57.  DynamicArray<float> d2;
  58.  for(int i=0; i<6; i++){
  59.   d1[i] = 'a';
  60.  }
  61.  for(int i=0; i<6; i++){
  62.   d2[i] = (i + .5);
  63.  }
  64.  for(int i=0; i<d1.getSize(); i++){
  65.   cout<<d1[i]<<" "<<d2[i]<<endl;
  66.  }
  67. return 0;
  68. }
Converting the DynamicArray class into a class template increased its usefulness as it can now be used to hold different types of objects, even user-defined types. The above example gives a main() function showing the DynamicArray class template in use.

Resources


/ Fig.�11.2: tstack1.h

// Stack class template.

#ifndef TSTACK1_H

#define TSTACK1_H



template<>

class Stack {     



public:

Stack( int = 10 );  // default constructor (stack size 10)



// destructor

~Stack()

{

delete [] stackPtr;



} // end ~Stack destructor



bool push( const T& );  // push an element onto the stack

bool pop( T& );         // pop an element off the stack

// determine whether Stack is empty

bool isEmpty() const

{

return top == -1;



} // end function isEmpty



// determine whether Stack is full

bool isFull() const

{

return top == size - 1;



} // end function isFull



private:

int size;     // # of elements in the stack

int top;      // location of the top element

T *stackPtr;  // pointer to the stack



}; // end class Stack



// constructor

template<>      

Stack<>::Stack( int s )

{

size = s > 0 ? s : 10; 

top = -1;  // Stack initially empty

stackPtr = new T[ size ]; // allocate memory for elements



} // end Stack constructor



// push element onto stack;

// if successful, return true; otherwise, return false

template<>                       

bool Stack<>::push( const T &pushValue )

{

if ( !isFull() ) {

stackPtr[ ++top ] = pushValue;  // place item on Stack

return true;  // push successful



} // end if



return false;  // push unsuccessful



} // end function push



// pop element off stack;

// if successful, return true; otherwise, return false

template<>               

bool Stack<>::pop( T &popValue )

{

if ( !isEmpty() ) {

popValue = stackPtr[ top-- ];  // remove item from Stack

return true;  // pop successful



} // end if



return false;  // pop unsuccessful



} // end function pop





// Fig.�11.3: fig11_03.cpp

// Stack-class-template test program.

#include 



using std::cout;

using std::cin;

using std::endl;



#include "tstack1.h"  // Stack class template definition



int main()

{

Stack<> doubleStack( 5 );

double doubleValue = 1.1;



cout << "Pushing elements onto doubleStack\n";



while ( doubleStack.push( doubleValue ) ) {

cout << doubleValue << ' ';

doubleValue += 1.1;



} // end while



cout << "\nStack is full. Cannot push " << doubleValue

<< "\n\nPopping elements from doubleStack\n";

while ( doubleStack.pop( doubleValue ) )

cout << doubleValue << ' ';



cout << "\nStack is empty. Cannot pop\n";



Stack<> intStack;

int intValue = 1;

cout << "\nPushing elements onto intStack\n";



while ( intStack.push( intValue ) ) {

cout << intValue << ' ';

++intValue;



} // end while



cout << "\nStack is full. Cannot push " << intValue

<< "\n\nPopping elements from intStack\n";



while ( intStack.pop( intValue ) ) 

cout << intValue << ' ';



cout << "\nStack is empty. Cannot pop\n";



return 0;

} // end main





#endif







2.http://www.functionx.com/cpp/Lesson07.htm

Steps to attack the problem sets

  • Step 1: Read about Static Polymorphism and try sample programs(DynamicArray) given in Static Polymorphism(You can copy paste the programs)
  • Step 2: Go through the PPT on Templates and try stack program from the PPT.(you can copy paste the program)
  • Step 3: Go through the additional resources given.
  • Step 4: Attack the problem sets in the given order(Problem Set A and then Problem Set B ...).
  • Step 5: If you are not clear with the problem sets then contact your respective mentor for clarification.
Spend atleast 2 hours from Step 1 to Step 3.

Problem Sets

Problem Set A

1. Create a function called swaps() that interchanges the values of the two arguments sent to it. (You will probably want to pass these arguments by reference.) Make the function into a template, so it can be used with all numerical data types (char, int, float, and so on). Write a main() program to exercise the function with several types.
Name the program as: PA1_swaps.cpp

Problem Set B

2. Create a function called amax() that returns the value of the largest element in an array. The arguments to the function should be the address of the array and its size. Make this function into a template so it will work with an array of any numerical type. Write a main() program that applies this function to arrays of various types.
Name the program as:PB1_amax.cpp

Problem Set C

3. A queue is a data-storage device. It’s like a stack, except that, instead of being last-in first-out, it’s first-in-first-out, like the line at a bank teller’s window. If you put in 1, 2, 3,you get back 1, 2, 3 in that order.
A stack needs only one index to an array (i.e top of the array). A queue, on the other hand, must keep track of two indexes to an array: one to the tail, where new items are added, and one to the head, where old items are removed. The tail follows the head through the array as items are added and removed. If either the tail or the head reaches the end of the array, it is reset to the beginning.
Write a class template for a queue class. Assume that the programmer using the queue won’t make any mistakes, like exceeding the capacity of the queue or trying to remove an item when the queue is empty. Define several queues of different data types and insert and remove data from them.
Use the dynamicarray discussed previously for implementing the queue.
Write the declaration and defining of Queue template in separate header file and a Test program to test the queue in separate source file.


Comments

Popular posts from this blog

Mini Project - Railway Reservation System

Module 1 Overview Railway Reservation System-(RRS) Project is developed to help students learn and realize capability of C programming language, and appreciate procedural approach of developing a system. RRS helps users reserve/cancel berths, search for trains, check reservation/cancellation history, etc... On the other hand it helps the railway reservation officers to prepare reservation charts, organize train schedules, etc... RRS provides two types of log-ins one is the User log-in for railway customers and the other one is Admin log-in for railway reservation officers. Project is divided into 10 modules of four hours duration each. All the modules will give exactly what to develop in the 4 hour time frame. Since most of the modules are inter-related it is important to keep track of the previous code. After submission it is allowed to make changes in the previous code to suit / integrate well to the system. Before we start the project, we need to make the followin...

Inheritance

Week 1 - M5 : Inheritance Learning Objectives At the end of this module, you will be able to Understand the implementation of inheritance in C++ Override the base class members Access overridden base class members using the scope resolution operator Understand the concept of base class initialization Introduction Inheritance The philosophy behind inheritance is to portray things as they exist in the real world. As inheritance is found in real world, it is important feature of OO programming. Inheritance has many advantages, the most important of them being the resuability of code. Once a class is defined and debugged, it can be used to create new subclasses. The reuse of existing class saves time and effort. The Class from which another class is derived is called the base class . The class which inherits the properties of the base class is called the derived class . Each instance of the derived class includes all the members of the base class. Since...