You are on page 1of 55

Templates

Templates
• The template is one of C++'s most sophisticated and high-powered
features.
• Although not part of the original specification for C++, it was added
several years ago and is supported by all modern C++ compilers.
• Using templates, it is possible to create generic functions and classes.
• In a generic function or class, the type of data upon which the
function or class operates is specified as a parameter.
• Thus, you can use one function or class with several different types of
data without having to explicitly recode specific versions for each data
type.
Generic Functions
• A generic function defines a general set of operations that will be
applied to various types of data.
• The type of data that the function will operate upon is passed to it as
a parameter.
• Through a generic function, a single general procedure can be applied
to a wide range of data. As you probably know, many algorithms are
logically the same no matter what type of data is being operated
upon.
• For example, the Quicksort sorting
• algorithm is the same whether it is applied to an array of integers or
an array of floats.
• It is just that the type of the data being sorted is different.
Generic Functions
• By creating a generic function, you can define the nature of the
algorithm, independent of any data.
• Once you have done this, the compiler will automatically generate the
correct code for the type of data that is actually used when you execute
the function.
• In essence, when you create a generic function you are creating a
function that can automatically overload itself.
• A generic function is created using the keyword template.
• The normal meaning of the word "template" accurately reflects its use
in C++.
• It is used to create a template (or framework) that describes what a
function will do, leaving it to the compiler to fill in the details as
needed.
Generic Functions
• The general form of a template function definition is shown here:

template <class Ttype> ret-type func-name(parameter list)


{
// body of function
}
• Ttype is a placeholder name for a data type used by the function.
• This name may be used within the function definition.
• However, it is only a placeholder that the compiler will automatically replace
with an actual data type when it creates a specific version of the function.
• Although the use of the keyword class to specify a generic type in a template
declaration is traditional.
Function Template

• When you create a template


function, you are, in
essence, allowing the
compiler to generate as
many different versions of
that function as are
necessary for handling the
various ways that your
program calls the function.
// This is a function template.
#include <iostream> int main()
using namespace std; {
int i=10, j=20;
template <class X>
double x=10.1, y=23.3;
void swapargs(X &a, X &b)
char a='x', b='z';
{ cout << "Original i, j: " << i << ' ' << j << '\n';
X temp; cout << "Original x, y: " << x << ' ' << y << '\n';
temp = a; cout << "Original a, b: " << a << ' ' << b << '\n';
a = b;
b = temp;
}
swapargs(i, j); // swap integers
swapargs(x, y); // swap floats
swapargs(a, b); // swap chars
cout << "Swapped i, j: " << i << ' ' << j << '\n';
cout << "Swapped x, y: " << x << ' ' << y << '\n';
cout << "Swapped a, b: " << a << ' ' << b << '\n';
return 0;
}
A Function with Two Generic Types
#include <iostream>
using namespace std;
template <class type1, class type2>
void myfunc(type1 x, type2 y)
{
cout << x << ' ' << y << '\n';
}
int main()
{
myfunc(10, "I like C++");
myfunc(98.6, 19L);
return 0;
}
Explicitly Overloading a Generic Function
template <class X> void swapargs(X &a, X &b){ int main()
X temp; {
temp = a; int i=10, j=20;
a = b; double x=10.1, y=23.3;
b = temp; char a='x', b='z’;
cout << "Inside template swapargs.\n"; swapargs(i, j); // calls explicitly overloaded swapargs()
} swapargs(x, y); // calls generic swapargs()
// This overrides the generic version of swapargs(a, b); // calls generic swapargs()
swapargs() for ints. cout << "Swapped i, j: " << i << ' ' << j << '\n';
void swapargs(int &a, int &b){ cout << "Swapped x, y: " << x << ' ' << y << '\n';
int temp; cout << "Swapped a, b: " << a << ' ' << b << '\n';
temp = a; return 0;
a = b; }
b = temp;
cout << "Inside swapargs int specialization.\n";
}
OUTPUT
Inside swapargs int specialization.
Inside template swapargs.
Inside template swapargs.
Swapped i, j: 20 10
Swapped x, y: 23.3 10.1
Swapped a, b: z x
Overloading a Function Template
#include <iostream> int main()
using namespace std; {
// First version of f() template. f(10); // calls f(X)
template <class X> void f(X a) f(10, 20); // calls f(X, Y)
{ return 0;
cout << "Inside f(X a)\n"; f(10.1,20);
} f(‘h’);
// Second version of f() template. f(“ghhj”,4);
template <class X, class Y> }
void f(X a, Y b)
{
cout << "Inside f(X a, Y b)\n";
}
Generic Function Restrictions
• Generic functions are similar to overloaded functions except that they
are more restrictive.
• When functions are overloaded, you may have different actions
performed within the body of each function.
• But a generic function must perform the same general action for all
versions—only the type of data can differ.
• Consider the overloaded functions in the following example program.
• These functions could not be replaced by a generic function because
they do not do the same thing.
#include <iostream> cout << "Fractional part: " <<
fracpart;
#include <cmath>
cout << "\n";
using namespace std;
cout << "Integer part: " << intpart;*/
void myfunc(int i)
cout <<d;
{
}
cout << "value is: " << i << "\n";
int main()
}
{
void myfunc(double d)
myfunc(1);
{
myfunc(12.2);
/*double intpart;
return 0;
double fracpart;
}
fracpart = modf(d, &intpart);
Some Errors in Template Declaration
• No-argument template Function Usage of Partial Number of Template
template <class T> template <class T, class U>
T pop(void) void insert (T & X)
{ {
return *--Stack; U var1;;
} ………………………..
• Template Type Argument Unused }
template <class T>
void Test( int x)
{
T temp;
………..
…………
}
Default Arguments in template
#include<iostream> Inside f(X a, Y b)
using namespace std; 5 5.5
template <class X, class Y=double> Inside f(X a, Y b)
void f(X a, Y b=5.5) 10 20
{ Inside f(X a, Y b)
cout << "Inside f(X a, Y b)\n"; 10.1 20.9
cout<<a<< " "<<b<<endl; Inside f(X a, Y b)
}
h 5.5
int main(){
Inside f(X a, Y b)
f(5);
f(10, 20);
ghhj 4
f(10.1,20.9);
f('h');
f("ghhj",4);
return 0;
}
Generic Classes
• In addition to generic
functions, you can also define
a generic class.
• When you do this, you create a
class that defines all the
algorithms used by that class;
• however, the actual type of
the data being manipulated
will be specified as a
parameter when objects of
that class are created.
• Generic classes are useful
when a class uses logic that
can be generalized.
• For example, the same
algorithms that maintain a
queue of integers will also
work for a queue of
characters.
Generic Classes
const int MAX = 100;
template <class Type> Int main(){
class Stack Stack<float> s1; //s1 is object of class Stack<float>
{ s1.push(1111.1F);
private: Stack<char> s2;
Type st[MAX]; //stack: array of any type s2.push('x');
int top; //number of top of stack s2.push('y');
public: s2.push('z’);
Stack() //constructor Stack<double> ds1, ds2; // create two double stacks
{ top = -1; } ds1.push(1.1);
void push(Type var) //put number on stack ds2.push(2.2);
{ st[++top] = var; } cout<<s1.pop();
Type pop() //take number off stack cout<<s2.pop();
{ return st[top--]; } cout<<s2.pop();
}; return 0;
}
An Example with Two Generic Data Types
#include<iostream> int main()
using namespace std; {
template <class type1,class type2> X<int,double> obj1(5,8.9);
class X obj1.show();
{ X<double, char> obj2(3.4,'u');
type1 x1; obj2.show();
type2 x2; return 0;
public: }
X(type1 a, type2 b)
{ x1=a;
x2=b;
}
void show() { cout<<x1<<"
"<<x2<<endl; }
};
Two Generic Data Types with default arguments
#include<iostream> int main()
using namespace std; {
template <class type1=double, class X<int> obj1(5);
type2=int> obj1.show();
class X{ X<double, char> obj2(3.4,'u');
type1 x1; obj2.show();
type2 x2; X<> obj3;
public: obj3.show();
X(type1 a=2.9, type2 b=9) { return 0;
x1=a; }
x2=b; //output
} 59
void show() { cout<<x1<<" "<<x2<<endl; 3.4 u
}
2.9 9
};
Using Non-Type Arguments with Generic Classes
#include <iostream>
using namespace std;
// Here, int size is a non-type argument.
template <class AType, int size>
class atype {
AType a[size]; // length of array is passed in size
public:
atype() {
int i;
for(i=0; i<size; i++) a[i] = i;
}
AType &operator[](int i);
};
// Provide range checking for atype.
template <class AType, int size> int i;
AType &atype<AType, size>::operator[](int i) cout << "Integer array: ";
{ for(i=0; i<10; i++) intob[i] = i;
if(i<0 || i> size-1) { for(i=0; i<10; i++) cout << intob[i] << " ";
cout << "\nIndex value of "; cout << '\n';
cout << i << " is out-of-bounds.\n"; cout << "Double array: ";
exit(1); for(i=0; i<15; i++) doubleob[i] = (double) i/3;
} for(i=0; i<15; i++) cout << doubleob[i] << " ";
return a[i]; cout << '\n';
} intob[12] = 100; // generates runtime error
int main() return 0;
{ }
atype<int, 10> intob;
atype<double, 15> doubleob;
#include <iostream> void show();
#include <cstdlib> void getint()
using namespace std; {
template <class AType, int size=5> for(int i=0; i<size; i++) a[i] = i;
class atype { }
AType a[size]; void getdouble()
public: {
atype() { for(int i=0; i<size; i++) a[i] =
int i; (double)i/3;
for(i=0; i<size; i++) a[i] = i; }
} };
AType &operator[](int i);
template <class AType, int size> template <class AType, int size>
AType &atype<AType, void atype<AType,size> ::show()
size>::operator[](int i) {
{ for(int i=0; i<size; i++)
if(i<0 || i> size-1) { cout<<a[i]<<" ";
cout << "\nIndex value of "; }
cout << i << " is out-of-bounds.\n";
exit(1);
}
return a[i];
}
int main()
{ Integer array:
atype<int> intob; 01234
atype<double, 15> doubleob; Double array
cout << "Integer array: "; 0 0.333333 0.666667 1 1.33333
1.66667 2 2.33333 2.66667 3
intob.getint(); 3.33333 3.66667 4 4.33333
intob.show(); 4.66667
cout << “Double array: ";
doubleob.getdouble();
doubleob.show();
Exception Handling
Exception Mechanism
• The exception mechanism uses three new C++ keywords:
throw, catch, and try.
• In the most general terms, program statements that you want to
monitor for exceptions are contained in a try block.
• If an exception (i.e., an error) occurs within the try block, it is thrown
(using throw).
• The exception is caught, using catch, and processed.
Sequence of Events
• Code is executing normally outside a try block.
• Control enters the try block.
• A statement in the try block causes an error in a member function.
• The member function throws an exception.
• Control transfers to the exception handler (catch block) following the
try block.
Simple Example
#include <iostream> Start
using namespace std; Inside try block
int main(){ Caught an exception --
value is: 100
cout << "Start\n"; End
try { // start a try block
cout << "Inside try block\n";
throw 100; // throw an error
cout << "This will not execute";
}
catch (int i) { // catch an error
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
Simple Example which won’t work
#include <iostream> Start
using namespace std; Inside try block
int main(){ Abnormal program
termination
cout << "Start\n";
try { // start a try block
cout << "Inside try block\n";
throw 100; // throw an error
cout << "This will not execute";
}
catch (double i) { // won’t catch for an int an error
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
int main()
Simple Example {
#include<iostream> Number n1,n2;
using namespace std; int res;
class Number{ cout<<"enter number1"<<endl;
int num; n1.getnum();
public: cout<<"enter number2"<<endl;
void getnum() { cin>>num; } n2.getnum();
class Divide{}; try{
int div(Number n2) cout<<"Division"<<endl;
{ if(n2.num==0) res=n1.div(n2);
throw Divide(); cout<<"succeeded";
else }
return (num/n2.num); catch(Number::Divide)
} {
}; cout<<"divide by zero error";
return 1;
}
cout<<"result"<<res;
}
Using Multiple catch Statements
void Xhandler(int test) int main()
{ {
try{ cout << "Start\n";
if(test) throw test; Xhandler(1);
else throw "Value is zero"; Xhandler(2);
} Xhandler(0);
catch(int i) { Xhandler(3);
cout << "Caught Exception #: " << i << '\n'; cout << "End";
} return 0;
catch(const char *str) { }
cout << "Caught a string: ";
cout << str << '\n';
}
}
Start
Caught Exception #: 1
Caught Exception #: 2
Caught a string: Value is zero
Caught Exception #: 3
End
Catching All Exceptions
#include <iostream> int main()
using namespace std; {
void Xhandler(int test) cout << "Start\n";
{ Xhandler(0);
try{ Xhandler(1);
if(test==0) throw test; // throw int Xhandler(2);
if(test==1) throw 'a'; // throw char cout << "End";
if(test==2) throw 123.23; // throw double return 0;}
}
//catch(int i){ cout<< “integer exception”;} Start
catch(...) { // catch all exceptions Caught One! // integer exception
cout << "Caught One!\n"; Caught One!
} Caught One!
} End
Rethrowing an Exception
#include <iostream> int main()
using namespace std; {
void Xhandler() cout << "Start\n";
{ try{
try { Xhandler();
throw "hello"; // throw a char * }
} catch(const char *) {
catch(const char *) { // catch a char * cout << "Caught char * inside main\n";
cout << "Caught char * inside }
Xhandler\n"; cout << "End";
throw ; // rethrow char * out of return 0;
function }
}
}
Start
Caught char * inside Xhandler
Caught char * inside main
End
List of Exception
• Raising or catching an exception affects the way a function relates to
other functions
• It is possible to specify a list of exceptions that a function can throw.
• This exception specification can be used as a suffix to the function
declaration specifying the list of exceptions that a function may throw
as a part of function declaration
• Syntax
Example exp5.cpp
int main()
#include <iostream> { int num;

using namespace std; cout<<"Enter number"<<endl;


cin>>num;
class positive{}; try{
check_sign(num);
class negative{};
}
class zero{}; catch(positive)
{
void check_sign(int num) throw
cout<<"Exception for +ve"<<endl;
(positive,negative)
}
{ if(num>0) catch(negative)
{
throw positive();
cout<<"Exception for -ve"<<endl;
else if(num<0) }
catch(zero)
throw negative();
{
else cout<<"Exception for Zero"<<endl;
}
throw zero();
return 0;
} }
Example
void check_sign(int num) throw (positive,negative)
{….
}
This function check_sign() can specify list of exception it can throw.
Here it can throw positive, negative exception
No other exception will propagate out of this function.
Any other exception other than positive and negative, here zero is
considered as an unexpected exception and program control will be
transferred to the predefined unexpected function.
Output exp5.cpp
• Run1
Enter number
4
Exception for +ve
• Run2
Enter number
-9
Exception for -ve
• Run3
Enter number
0
terminate called after throwing an instance of 'zero'
Namespace
Namespace
• The namespace keyword allows you to partition the global
namespace by creating a declarative region.
• In essence, a namespace defines a scope.
• The general form of namespace is shown here:
namespace name {
// declarations
}
• Anything defined within a namespace statement is within the scope
of that namespace.
• As you can imagine, if your program includes frequent references to
the members of a namespace, having to specify the namespace and
the scope resolution operator each time you need to refer to one
quickly becomes a tedious chore.
• The using statement was invented to alleviate this problem.
• The using statement has these two general forms:
using namespace name;
using name::member;
namespace geo • Accessing Namespace Members
outside the namespace
{
• double c = circumf(10); //won’t
const double PI = 3.14159; work here
double circumf(double radius) • double c = geo::circumf(10);
{ return 2 * PI * radius; } //OK
} //end namespace geo
Example
#include <iostream> void reset(int n) {
using namespace std; if(n <= upperbound) count = n;
namespace CounterNameSpace { }
int upperbound; int run() {
int lowerbound; if(count > lowerbound) return count--;
class counter { else return lowerbound;
int count; }
public: };
counter(int n) { }
if(n <= upperbound) count = n;
else count = upperbound;
}
Example
int main() CounterNameSpace::counter ob2(20);
{ do {
CounterNameSpace::upperbound = 100; i = ob2.run();
CounterNameSpace::lowerbound = 0; cout << i << " ";
CounterNameSpace::counter ob1(10); } while(i > CounterNameSpace::lowerbound);
int i; cout << endl;
do { ob2.reset(100);
i = ob1.run(); CounterNameSpace::lowerbound = 90;
cout << i << " "; do {
} while(i > CounterNameSpace::lowerbound); i = ob2.run();
cout << endl; cout << i << " ";
} while(i > CounterNameSpace::lowerbound);
return 0;
}
output
10 9 8 7 6 5 4 3 2 1 0
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
100 99 98 97 96 95 94 93 92 91 90
Some Namespace Options
• There may be more than one namespace declaration of the same name.
This allows a
• namespace to be split over several files or even separated within the same
file.
#include <iostream>
using namespace std;
namespace NS {
int i;
}
// ...
namespace NS {
int j;
}
Some Namespace Options Example
int main() • Output
{ • 100
NS::i = NS::j = 10; • 100
// refer to NS specifically • Here, NS is split into two pieces.
However, the contents of each
cout << NS::i * NS::j << "\n"; piece are still within the same
// use NS namespace namespace, that is, NS.
using namespace NS;
cout << i * j;
return 0;
}
Some Namespace Options
• A namespace must be declared #include <iostream>
outside of all other scopes. using namespace std;
• This means that you cannot namespace NS1 {
declare namespaces that are int i;
localized to a function, for
example. namespace NS2 { // a nested
namespace
• There is, however, one
exception: a namespace can be int j;
nested within another. }
• Consider this program: }
Some Namespace Options
int main() This program produces the following
output:
{ 19 10
NS1::i = 19; 190
// NS2::j = 10; Error, NS2 is not in view Here, the namespace NS2 is nested within
NS1::NS2::j = 10; // this is right NS1.
cout << NS1::i << " "<< NS1::NS2::j << "\n"; Thus, when the program begins, to
// use NS1 refer to j, you must qualify it with both the
using namespace NS1; NS1 and NS2 namespaces.
/* Now that NS1 is in view, NS2 can be used NS2 by itself is insufficient. After the
to statement
refer to j. */ using namespace NS1;
cout << i * NS2::j;
return 0;
}
Thank you
End of Module II

You might also like