Since C++ supports procedural as
well as object oriented approach, we will use templates to implement generic
functions as well as generic classes. As usual lets start with generic
functions.
Generic functions:
A generic fn defines a general set
of operations that will be applied to various types of data. The type of data
the fn will operate on will be passed to it as a parameter. Best real time eg
is sorting algorithms. The algorithm is going to be same if it s applied to an
array of integers of array of floats. It s jus the type of data being sorted is
different.
By creating a generic fn, u r jus
defining the nature of the algorithm, independent of any data. Once u have done
this, the compiler will automatically generate the correct code for the type of
data that is actually used when u execute the fn. In other words, when u create
a generic fn u are creating a fn that can automatically overload itself.
A generic fn is created using the
keyword template. The general form of a template fn definition is
template
<class Ttype> return type fn name(parameter list)
{
//body of fn
}
Here
Ttype is the place holder name for a data type used by the fn and this name
will be used with in the fn. This place holder will be automatically replaced
with an actual data type when it creates a specific version of the fn.
Lets
take a simple swap as an example and explain things based on that
#include
<iostream>
using
namespace std;
template
<class Ttype>
void
Swap_Values(Ttype&, Ttype&);
int
main() {
int a = 10, b = 20;
cout << "Value of a before
swapping = " << a << endl;
cout << "Value of b before
swapping = " << b << endl;
Swap_Values <int> (a, b);
cout << "Value of a after
swapping = " << a << endl;
cout << "Value of b after
swapping = " << b << endl;
float c = 12.2, d = 22.2;
cout << "Value of c before
swapping = " << c << endl;
cout << "Value of d before
swapping = " << d << endl;
Swap_Values <float> (c, d);
cout << "Value of a after
swapping = " << c << endl;
cout << "Value of b after
swapping = " << d << endl;
char e = 'a', f = 'b';
cout << "Value of e before
swapping = " << e << endl;
cout << "Value of f before
swapping = " << f << endl;
Swap_Values <char> (e, f);
cout << "Value of e after
swapping = " << e << endl;
cout << "Value of f after
swapping = " << f << endl;
return 0;
}
template
<class Ttype>
void
Swap_Values(Ttype &p, Ttype &q)
{
Ttype Temp;
Temp = p;
p = q;
q = Temp;
}
Now
lets analyse the above program. The statement
template
<class T> void Swap_Values(Ttype &, Ttype &)
tells
the compiler two things. Firstly the template is being created and that a
generic definition is beginning.
Here Type is the place holder. Now
in our main we have three calls for the fn Swap_Values(). Lets take the first
one. When the compiler sees the fn call, first it checks what the data type of
a and b is . then that data type is passed as an argument.ie) it replaces T by
int. Then it generates a swap function corresponding to integer and stores it
in memory.
Similarly in the second case, T will
be replaced by float and a version of swaparg() corresponding to float s will
be created and kept in another section of memory and the same process is
repeated for char also.
Now u should be very familiar with
some words related to templates. First one is template functions.
- A generic
function is also known as template function.
- When the
compiler creates a specific version of this function, it is said to have
created a specialization. This is also called a generated function.
- The act of
generating a function is referred to as instantiating it. Or generated fn
is a specific instance of a template fn.
Now
lets go back to the above program itself. Let me include one more function call
there to swap two integers again.
#include
<iostream>
using
namespace std;
template
<class Ttype>
void
Swap_Values(Ttype&, Ttype&);
int
main() {
int a = 10, b = 20;
cout << "Value of a before
swapping = " << a << endl;
cout << "Value of b before
swapping = " << b << endl;
Swap_Values <int> (a, b);
cout << "Value of a after
swapping = " << a << endl;
cout << "Value of b after
swapping = " << b << endl;
float c = 12.2, d = 22.2;
cout << "Value of c before
swapping = " << c << endl;
cout << "Value of d before
swapping = " << d << endl;
Swap_Values <float> (c, d);
cout << "Value of a after
swapping = " << c << endl;
cout << "Value of b after
swapping = " << d << endl;
char e = 'a', f = 'b';
cout << "Value of e before
swapping = " << e << endl;
cout << "Value of f before
swapping = " << f << endl;
Swap_Values <char> (e, f);
cout << "Value of e after
swapping = " << e << endl;
cout << "Value of f after
swapping = " << f << endl;
int g = 100, h = 200;
cout << "Value of g before
swapping = " << g << endl;
cout << "Value of h before
swapping = " << h << endl;
Swap_Values <int> (g, h);
cout << "Value of g after
swapping = " << g << endl;
cout << "Value of h after
swapping = " << h << endl;
return 0;
}
template
<class Ttype>
void
Swap_Values(Ttype &p, Ttype &q)
{
Ttype Temp;
Temp = p;
p = q;
q = Temp;
}
In
this case, the compiler won’t be creating a new version for this integer fn
call, instead it will be retrieving the integer fn generated and kept in the
memory when the swap call Swap_Values(a, b) occurred.
A
function with two generic types:
In the above eg we were dealing with
a single data type. Now it is even possible to use two data types in the same
template fn. Lets c an eg:
template
<class T1, class T2> void display(T1, T2);
main()
{
int i = 10;
char a = 'a';
display(i, a);
}
template
<class T1, class T2>
void
display(T1 p, T2 q)
{
cout << p << endl <<
q << endl;
}
Here first compiler sees the fn call and
replaces T1 with int and T2 with char and creates a version corresponding to
it. Rest all remains the same.
Consider the below program for finding
the maximum of two numbers
#include
<iostream>
using
namespace std;
template
<class Ttype>
void
Max_Values(Ttype,Ttype);
int
main() {
int a = 10, b = 20;
Max_Values(a, b);
char d[] = "world", c[] =
"hello";
Max_Values(c, d);
}
template
<class Ttype>
void
Max_Values(Ttype v1, Ttype v2) {
if (v1 > v2)
cout << "greater one
is " << v1 << endl;
else
cout << "greater one
is " << v2 << endl;
}
Here
the function works perfectly for integers or even for float or double, but when
it comes to strings, the comparison goes wrong. When we use > operator, it
simply checks the address where the strings are placed and never the ascii
values of the characters in the string. Usually for string comparison, we have
to go for our strcmp function. So the above function doesn’t hold good for
strings.
In such cases if ever we can create
a situation where in , when data type is string, it calls another function for
the comparison and not this function, wont it be better. Such a flexibility is
also there in templates and it can be done in two different ways.
1.
Explicit
specialization
2.
Template
overriding
Explicit
specialization :
The syntax is
template <>
return_type
function_name( arguments);
modifying
the above program :
#include
<iostream>
using
namespace std;
template
<class Ttype>
void
Max_Values(Ttype,Ttype);
template
<>
void
Max_Values(char*, char*);
int
main() {
int a = 10, b = 20;
Max_Values(a, b);
char d[] = "abc", c[] =
"hello";
Max_Values(c, d);
}
template
<class Ttype>
void
Max_Values(Ttype v1, Ttype v2) {
if (v1 > v2)
cout << "greater one
is " << v1 << endl;
else
cout << "greater one
is " << v2 << endl;
}
template
<>
void
Max_Values(char *p, char *q) {
int v;
v = strcmp(p, q);
if(v == 0)
cout << "Both are
equal" << endl;
else if(v > 0)
cout << "the bigger
string is " << p << endl;
else
cout << "the bigger
string is " << q << endl;
}
Explicitly
overloading a generic function:
void swaparg(int
&p, int &q);
template
<class T> void swaparg(T &p, T &q);
main()
{
int a = 10, b = 20;
cout << a << endl <<
b << endl;
swaparg(a, b);
cout << a << endl <<
b << endl;
}
void swaparg(int
&p, int &q)
{
cout << "explicit fn"
<< endl;
int temp;
temp = p;
p = q;
q = temp;
}
template
<class T>
void swaparg(T
&p, T &q)
{
cout << "template fn"
<< endl;
T temp;
temp = p;
p = q;
q = temp;
}
can
u tell me what the output is :
10
20
explicit fn
20
10
so
what s happening here. In the above program u r writing a template fn as well
as a normal fn. That s u r overloading the swap fn. But c the output. Even
though the template fn is present, the compiler calls only the explicitly
overloaded fn. Thus the compiler doesn’t generate the version of generic
swapargs function because the generic fn is overridden by the explicit
overloading.
So now the use of this explicit
overloading: it allows you to tailor a version of a generic fn to accommodate a
unique situation – perhaps to take advantage of some performance boost that
applies to only one type of data.
Now remember it is even possible to
overload your template functions. C it in “complete reference” and if u have
any doubt u come and ask me ok.
Generic function
restrictions:
Generic fn are similar to overloaded
fn, but they have one restriction. When fn are overloaded, we can have
different actions performed with in the body of each fn. But a generic fn must
perform the same general actions for all versions, only data type should vary.
Generic
classes:
It is possible to write generic
classes in the same manner u which u created generic fn. When u do this u will
be creating a class that defines all the algorithms used by a class, however
the actual type of data being manipulated will be specified as a parameter when
objects of that class is created.
Lets
take a stack eg. Stack is actually a general algorithm which can be used to
push and pop integers, floats or char etc. so it will be always better if we
can create a generic class for that. Lets write the program and explain things
based on that.
const int MAX =
20;
template
<class S>
class stack {
private:
S stk[MAX];
int tos;
public:
stack() {
tos = 0;
}
void push(S data) {
if (tos == MAX) {
cout <<
"stack is full" << endl;
return;
}
else {
stk[tos] =
data;
tos++;
}
}
S pop() {
if (tos == 0) {
cout <<
"stack is empty";
return 0;
}
else {
tos--;
return
stk[tos];
}
}
};
main()
{
stack <int> s1;
s1.push(10);
s1.push(20);
s1.push(30);
cout << s1.pop() << endl;
cout << s1.pop() <<
endl;
cout << s1.pop() << endl;
stack <char> s2;
s2.push('a');
s2.push('b');
s2.push('c');
cout << s2.pop() << endl;
cout << s2.pop() << endl;
cout << s2.pop() << endl;
}
now
if I am writing it the fn outside, u have to take care of one thing. Whenever
the class name comes u have to specify the place holder format there as shown
in the following program.
const int MAX =
20;
template
<class S>
class stack {
private:
S stk[MAX];
int tos;
public:
stack() {
tos = 0;
}
void push(S data);
S pop();
};
template
<class S> void stack <S> :: push(S data) {
if (tos == MAX) {
cout << "stack is
full" << endl;
return;
}
else
{
stk[tos] = data;
tos++;
}
}
template
<class S> S stack<S> :: pop()
{
if (tos == 0) {
cout << "stack is
empty" << endl;
return 0;
}
else
{
tos--;
return stk[tos];
}
}
main()
{
stack <int> s1;
s1.push(10);
s1.push(20);
s1.push(30);
cout << s1.pop() << endl;
cout << s1.pop() <<
endl;
cout << s1.pop() << endl;
stack <char> s2;
s2.push('a');
s2.push('b');
s2.push('c');
cout << s2.pop() << endl;
cout << s2.pop() << endl;
cout << s2.pop() << endl;
}
An eg with two
generic data types:
Let
s c an eg:
template
<class T1, class T2>
class
sample {
private:
T1 x;
T2 y;
public:
sample(T1 p, T2 q) {
x = p;
y = q;
}
void display();
};
template
<class T1, class T2> void sample <T1, T2> :: display()
{
cout << x << endl;
cout << y << endl;
}
main()
{
sample <int, char> obj(10, 'a');
obj.display();
}
0 comments:
Post a Comment