Title: Templates and the Quest for Generics
1Templates and the Quest for Generics
Based partly on presentations by Richard Chang _at_
UMBC, Templates Brian J. Higgs, C Class
Templates
2What's the Problem?
- // File stack1.h version 1 of 27 Oct 2007
- class Stack
- public
- Stack ( int maxSize )
- base( new int size maxSize )
- top base
-
- Stack()
- delete base
-
- void Push ( int item )
- top item
-
- int Pop()
- return --top
-
- int Size() const
- return top - base
- Consider a Stack class that implements a stack of
ints
3What's the Problem?
- Here is how you could use it
- include ltiostreamgt
- include stack1.h
- using stdcout
- using stdendl
- int main()
- Stack mystack ( 100 )
- int i
- for ( i 0 i lt 10 i )
- mystack.Push ( i )
- cout ltlt "Size " ltlt mystack.Size() ltlt endl
- ltlt "Values "
- for ( i 0 i lt 10 i )
- cout ltlt mystack.Pop() ltlt ' '
- cout ltlt endl
- return 0
4What's the Problem?
- That's all well and good for ints, but what about
other types? - We could write new classes as needed
- StackOfInts
- StackOfLongs
- StackOfChars
- StackOfChickenliver
- but this is repetitive, tedious, error-prone
and pollutes the namespace with extra
identifiers. - The implementation of something like a Stack is
largely independent on what type the stack
supports. The algorithms remain identical only
the types change. - So why cant we have one solution that works for
all types?
5One Solution Use typedefs
- Somehow we need to parameterize the class
definition, so it can handle a variety of
different data types - This would make it more general-purpose ---
generic for short - And we could avoid a lot of stupid, repetitive
work - We call this lifting the class because we are
raising it to a higher level of abstraction - One form of lifting involves the use of typedef
as a quick and dirty hack
6One Solution Use typedefs
- Note that Version 2 is identical to Version 1,
except that every int that relates to the type of
stack has been changed to DT (shorthand for Data
Type) - Other ints that do not relate to the type of
stack are left unchanged - Hey, I dont see a typedef!
- // File stack2.h version 2 of 27 Oct 2007
- class Stack
- public
- Stack ( int maxSize )
- base( new DT size maxSize )
- top base
-
- Stack()
- delete base
-
- void Push ( DT item )
- top item
-
- DT Pop()
- return --top
-
- int Size() const
- return top - base
7The typedef solution
- Okay, now I see the typedef
- We say line 2 typedefs DT to char star
- Important
- typedef must come before we include stack2.h
- Otherwise, DT would be undefined in stack2.h
- include ltiostreamgt
- typedef char DT // Make DT a synonym for char
- include stack2.h
- using stdcout
- using stdendl
- int main()
- Stack mystack ( 100 )
- int i
- for ( i 0 i lt 10 i )
- mystack.Push ( i )
- cout ltlt "Size " ltlt mystack.Size() ltlt endl
- ltlt "Values "
- for ( i 0 i lt 10 i )
- cout ltlt mystack.Pop() ltlt ' '
- cout ltlt endl
8The typedef solution Less than perfect
- Difficult to extend this solution to handle more
than one kind of stack in the same compilation
unit - Would require writing macros
- Client program must remember to issue the
required typedef before the include, otherwise
ka-boom - typedef is a form of aliasing, with all the usual
risks
9Templates A Real Solution
- // File stack3.h version 3 of 27 Oct 2007
- template ltclass DTgt
- class Stack
- public
- Stack ( int maxSize )
- base( new DT size maxSize )
- top base
-
- Stack()
- delete base
-
- void Push ( DT item )
- top item
-
- DT Pop()
- return --top
-
- int Size() const
- Note that Version 3 is identical to Version 2,
except for the addition oftemplate ltclass DTgt - Also correct to usetemplate lttypename DTgtto
avoid the double occurrence of the token class
10The Real Solution Class Templates
- include ltiostreamgt
- include "stack3.h"
- using stdcout
- using stdendl
- int main()
- Stackltchar gt mystack( 100 )
- char values "Mabel", "George", "Joe"
- int i
- for ( i 0 i lt 3 i )
- mystack.Push ( valuesi )
- cout ltlt "Size " ltlt mystack.Size() ltlt endl
- ltlt "Values "
- for ( i 0 i lt 3 i )
- cout ltlt mystack.Pop() ltlt ' '
- cout ltlt endl
- return 0
- When a variable, like mystack, is declared as an
instance of a template type, the declaration must
also parameterize the template - Tells the compiler what kind of stack
Size 3 Values Joe George Mabel
11Class Template Methods
- In the previous examples, all the Stack member
functions were declared and defined inline
(within the class body) - Declared, means the functions signature was
fully determined - Defined, means that there was also code to
implement the function - Like other member functions, these could have
been defined elsewhere, even in a separate file,
assuming the corresponding prototype declaration
exists in the Stack class - When we define them outside the class, we need
to repeat the type information and use a scope
resolution operator
DT Pop() return --top
template ltclass DTgt DT StackltDTgtPop()
return --top
12DigressionOther Uses for Scope Resolution
class-name identifier (already seen)
identifier namespace identifier
- int amount 123 // global
variable - void main()
- int amount 456 // local
variable - cout ltlt amount ltlt endl // Print the
global variable (123) - cout ltlt amount ltlt endl // Print the
local variable (456) -
- // We say line 4 scopes amount to global
namespace
- // using namespace std
- stdcout ltlt "hello" ltlt stdendl
- // still works, even without the using clause
- // We say line 2 scopes endl to namesapce std
13DigressionNontype Template Parameters
- There are actually two types of template
parameters - A template type parameter (what we have seen so
far) - class T or typename T
- A template nontype parameter
- int size , double value , etc.
- Nontype parameter example
- template ltint sizegt
- class Buffer
14Nontype Template Parameters
- include ltassert.hgt
- template ltint SIZEgt
- class CharArray
- public
- size_t getSize()
- return SIZE
-
- char operator ( int i )
- assert ( i gt 0 i lt SIZE )
- return m_bufferi
-
- private
- char m_bufferSIZE
-
15Nontype Template Parameters
- include ltiostreamgt
- include "chararray.h"
- using stdcout
- using stdendl
- int main()
- CharArraylt8gt ca1
- int i
- for ( i 0 i lt ca1.getSize() i )
- ca1i 'a' i
- for ( i 0 i lt ca1.getSize() i )
- cout ltlt "" ltlt i ltlt " " ltlt ca1i ltlt
endl - return 0
16Polymorphism
- Polymorphism The ability of a variable,
function, or class to take more than one form - C has two types of parametric polymorphism
- Function templates
- Class templates
17Function Templates
- Suppose you want to write a generic function
max() that, given two arguments of the same type,
will return the larger of the two - You could write a number of separate functions
int maxInt( int a, int b ) - int maxFloat( float a, float b )
-
- Foo maxFoo( const Foo a, const Foo b )
but that would be a lot of work
18Function Templates
- The code in each would look exactly the same
- if ( a lt b )
- return b
- else
- return a
- Hmmm the algorithm does not rely on the
specific data type
19Function Templates
- Using templates, we can write one function to
handle (almost) all data types and classes - template ltclass DTgt
- DT max( const DT a, const DT b )
- if ( a lt b )
- return b
- else
- return a
-
- DT is called the type parameter
- It doesnt have to be DT it can be any legal
identifier
20max( ) for int
- When compiler sees this code
- int i1 1, i2 2, i3i3 max( i1, i2 )
- it uses the template to create this function
- int max( const int a, const int b )
- if ( a lt b )
- return b
- else
- return a
21max( ) for floats
- When compiler sees this code
- float f1 1.2 f2 2.2, f3f3 max( f1, f2
) - it uses the template to create this function
- float max( const float a, const float b )
- if ( a lt b )
- return b
- else
- return a
22max( ) for Foo
- When compiler sees this code
- Foo F1 value1, F2 value2, F3F3 max( F1,
F2 ) - it creates this function
- Foo max( const Foo a, const Foo b )
- if ( a lt b )
- return b
- else
- return a
23Calling a Template Function
- Note that the function call to max( ) was not
changed - i3 max( i1, i2 ) f3 max( f1, f3 ) etc.
- The caller doesnt know its calling a template
function
24Can max( ) Be Created for Any Data Type?
- Answer -- Almost
- Note that max ( ) includes the statement
- if ( a lt b ) . . .
- If the data type is a class, that class must
support the lt operator (i.e., it must be
overloaded) - It is a good idea overload the relational
operators you never know how the class will be
used
25Can max( ) Be Created for Any Data Type?
- When the compiler sees this code
- char str1 abc
- char str2 def
- cout ltlt max( str1, str2 ) ltlt endl
- it creates this function
- char max( const char a, const char b )
- if ( a lt b ) // What gets compared?
- return b
- else
- return a
26A Workaround
- We can work around this problem by overloading
max() and providing an explicit function for char
- char max( char a, char b )
- if ( strcmp( a, b ) lt 0)
- return b
- else
- return a
-
- The compiler will look for an exact function
signature match before using the template
27smartArray of ints
- class smartArray
- public
- smartArray( int size 100 )
- // other members
- private
- int _size
- int _theData // this is what makes it
- // a smartArray of ints
- Nothing in the code for the smartArray relies on
the fact that this is an array of integers - So can lift smartArray into a template that
can be used for any primitive data type or
(almost) any class
28Lifted smartArray Class
- template ltclass Tgt
- class smartArray
- public
- smartArray( int size 100 )
- private
- int _size
- T _theData // array of any type
-
29A
Minimal requirements works with maximal family
of types
Generic algorithm
m
Lift
. . .
Lift
A
Remove an unneeded requirement on the type
Less specialized works with more than one type
1
Lift
A
Start here
Concrete algorithm requires specific data type
0
30Parameterized Constructor
- template ltclass Tgt
- smartArrayltTgtsmartArray( int size )
- _size size
- _theData new T _size
- for ( int j 0 j lt _size j )
- _theData j T( )
-
Whats this?
31Parameterized Copy Constructor
- template ltclass Tgt
- smartArrayltTgtsmartArray( const smartArrayltTgt a
) - _size a._size
- _theData new T _size
- for ( int j 0 j lt _size j )
- _theData j a._theData j
-
32Parameterized Copy Constructor -- Gotcha
- template ltclass Tgt
- smartArrayltTgtsmartArray( const smartArrayltTgt a
) - _size a._size
- _theData new T _size
- for ( T j 0 j lt _size j ) // Oops
- _theData j a._theData j
-
Bad promotion
33Parameterized operator
- template ltclass Tgt
- T smartArrayltTgtoperator ( int index )
- return _theData index
34Using smartArray
- In the main program
- include smartArray.h
- Define some smartArrays
- smartArrayltfloatgt array1 smartArrayltmyClassgt
array2 - When the compiler sees these definitions, it
looks for the smartArray template to determine
how to generate the needed class code
35Template .h and .cpp Files
- As usual, we put the class template in the .h
file and the implementation in the .cpp file. - Same for function templates
36How Does the Compiler Find the .cpp File?
- Varies from compiler to compiler
- Some compilers assume that the code for the
template found in file.h will be in file.cpp - assumes the same root filename
- The g compiler uses a dumb linker from the
70s that makes no assumptions - Why? You ask
37Templates and g
- For the g compiler to find the implementation
of a template, we must do one of the following - include the X.cpp file at the bottom of the X.h
file - When main.cpp includes X.h, the X.cpp file will
be included by implication - include X.cpp from main.cpp, then include X.h
at the top of X.cpp - When main.cpp includes X.cpp, the X.h will be
included by implication - This applies ONLY to templates ONLY to g
38Templates and g Solution 1
- // File smartArray.h
- ifndef SMARTARRAY_H
- define SMARTARRAY_H
- template ltclass Tgt
- class smartArray
- public
- smartArray ( int size 100 )
- // other members
- private
- int _size
- T _theData
-
- include smartArray.cpp
- endif
39Templates and g Solution 2
- // File main.cpp
- include "smartArray.cpp"
- int main()
- smartArrayltfloatgt array1
- //
- return 0
- // File smartArray.cpp
- include "smartArray.h"
- template ltclass Tgt
- smartArrayltTgtsmartArray( int size )
- _size size
- _theData new T _size
- for ( int j 0 j lt _size j )
- _theData j T( )
-
- //
40Templates and g
?
- // File smartArray.h
- ifndef SMARTARRAY_H
- define SMARTARRAY_H
- template ltclass Tgt
- class smartArray
- public
- smartArray ( int size 100 )
- // other members
- private
- int _size
- T _theData
-
- include smartArray.cpp
- endif
- // File main.cpp
- include "smartArray.cpp"
- int main()
- smartArrayltfloatgt array1
- //
- return 0
?
Which is better, solution ? or solution ? ?
- // File smartArray.cpp
- include "smartArray.h
- //
41Writing Templates
- Often the best ways to write a template is to
start with a version that works just with ints - Then, replace all the appropriate ints (not ALL
the ints) with the template type parameter
42Lifting Code
- Simple ?
- Pointer-based ?
- Typedef ?
- Template ?
- Range-based ?
- Fully Generic STL-ready
43File binsearch0.cpp
- // Shows conventional binary search function,
without pointers - const int binary_search ( int array, int n, int
x ) - int lo 0, hi n - 1, mid
- while ( lo lt hi )
- mid ( lo hi ) / 2
- if ( x array mid )
- return array mid
- if ( x lt array mid )
- hi mid - 1
- else
- lo mid 1
-
- return 0
44File binsearch1.cpp
- // Shows conventional binary search function with
pointers - const int binary_search ( const int array,
int n, int x ) - const int lo array, hi array n,
mid - while ( lo lt hi )
- mid lo ( hi - lo ) / 2
- if ( x mid )
- return mid
- if ( x lt mid )
- hi mid - 1
- else
- lo mid 1
-
- return 0
What tokens do we replace with the type parameter
T
45File binsearch2.cpp
- // Shows typedef "solution"
- typedef char T
- const T binary_search ( const T array, int n,
const T x ) - const T lo array, hi array n,
mid - while ( lo ! hi )
- mid lo ( hi - lo ) / 2
- if ( x mid )
- return mid
- if ( x lt mid )
- hi mid
- else
- lo mid 1
-
- return 0
Now were does the template stuff go?
46File binsearch3.cpp
- // Shows conventional binary search function made
into template - template lt class T gt
- const T binary_search ( const T array, int n,
const T x ) - const T lo array, hi array n,
mid - while ( lo ! hi )
- mid lo ( hi - lo ) / 2
- if ( x mid )
- return mid
- if ( x lt mid )
- hi mid
- else
- lo mid 1
-
- return 0
Whats the problem with the return value?
47File binsearch3.cpp main()
- int main()
- const int length 10
- char elements length
- const char found
- for ( int i 0 i lt length i )
- elements i char( i 65 )
- char target
- for ( int i -3 i lt length 3 i )
- target char( i 64 )
- found binary_search( elements, length,
target ) - if ( found )
- cout ltlt found
-
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
48File binsearch4.cpp
- // Shows binary search template with better
return value - include ltiostreamgt
- template lt class T gt
- const T binary_search ( const T array, int n,
const T x ) - const T lo array, hi array n,
mid - while ( lo ! hi )
- mid lo ( hi - lo ) / 2
- if ( x mid )
- return mid
- if ( x lt mid )
- hi mid
- else
- lo mid 1
-
- return array n
49File binsearch4.cpp main()
- int main()
- const int length 10
- char elements length
- const char found
- for ( int i 0 i lt length i )
- elements i char( i 65 )
- char target
- for ( int i -3 i lt length 3 i )
- target char( i 64 )
- found binary_search( elements, length,
target ) - if ( found ! elements length )
- cout ltlt found
-
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
50File binsearch5.cpp
- // Shows const-correct binary search template,
using first and last - include ltiostreamgt
- template lt class T gt
- const T binary_search ( const T first, const
T last, const T value ) - const T not_found last, mid
- while ( first ! last )
- mid first ( last - first ) / 2
- if ( value mid )
- return mid
- if ( value lt mid )
- last mid
- else
- first mid 1
-
- return not_found
51File binsearch5.cpp main()
- int main()
- const int length 10
- char elements length
- const char found
- for ( int i 0 i lt length i )
- elements i char( i 65 )
- char target
- for ( int i -3 i lt length 3 i )
- target char( i 64 )
- found binary_search( elements, elements
length, target ) - if ( found ! elements length )
- cout ltlt found
-
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
52File binsearch6.cpp
- // Shows binary search template, using STL
iterators - include ltvectorgt
- include ltiostreamgt
- template lt class RandomAccessIterator, class T gt
- RandomAccessIterator binary_search(
RandomAccessIterator first, -
RandomAccessIterator last, - const T
value ) - RandomAccessIterator not_found last, mid
- while ( first ! last )
- mid first ( last - first ) / 2
- if ( value mid )
- return mid
- if ( value lt mid )
- last mid
- else
- first mid 1
53File binsearch6.cpp main()
- int main()
- vector lt char gt v
- for ( int i 0 i lt 10 i )
- v.push_back( char( i 65 ) )
- vector lt char gt iterator itr
- char target
- for ( int i -3 i lt 13 i )
- target char( i 64 )
- itr binary_search( v.begin(), v.end(),
target ) - if ( itr ! v.end() )
- cout ltlt itr
-
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
- // ABCDEFGHIJ
54File selsort0.cpp
- // Shows naive version using globals
- include ltiostreamgt
- const int length 10
- int elements length
- void selectionSort()
- for ( int i 0 i lt length - 1 i )
- int s i
- int small elements s
- for ( int j i 1 j lt length j )
- if ( elements j lt small )
- s j
- small elements s
-
- elements s elements i
- elements i small
-
55File selsort1.cpp
- // Shows version with conventional parameters (no
globals) - include ltiostreamgt
- void selectionSort( int elements, int length )
- for ( int i 0 i lt length - 1 i )
- int s i
- int small elements s
- for ( int j i 1 j lt length j )
- if ( elements j lt small )
- s j
- small elements s
-
- elements s elements i
- elements i small
-
56File selsort2.cpp
- // Shows typedef "solution"
- include ltiostreamgt
- typedef int T
- void selectionSort( T elements, int length )
- for ( int i 0 i lt length - 1 i )
- int s i
- T small elements s
- for ( int j i 1 j lt length j )
- if ( elements j lt small )
- s j
- small elements s
-
- elements s elements i
- elements i small
-
57File selsort3.cpp
- // Shows conversion to a template
- include ltiostreamgt
- template lt class T gt
- void selectionSort( T elements, int length )
- for ( int i 0 i lt length - 1 i )
- int s i
- T small elements s
- for ( int j i 1 j lt length j )
- if ( elements j lt small )
- s j
- small elements s
-
- elements s elements i
- elements i small
-
58File selsort3.cpp main()
- int main()
- const int length 10
- char elements length
- for ( int i 0 i lt length i )
- elements i char( length - i 64 )
- selectionSort( elements, length )
- for ( int i 0 i lt length i )
- cout ltlt elements i ltlt " "
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
- // A B C D E F G H I J
59File selsort4.cpp
- // Shows conversion to use first and last
pointers - include ltiostreamgt
- template lt class T gt
- void selectionSort( T start, T end )
- for ( T where start where lt end where
) - T loc where
- T small loc
- for ( T inner where 1 inner lt end
inner ) - if ( inner lt loc )
- loc inner
- small loc
-
- loc where
- where small
-
60File selsort4.cpp main()
- int main()
- const int length 10
- char elements length
- char start elements
- char after elements length
- for ( int i 0 i lt length i )
- elements i char( length - i 64 )
- selectionSort( start, after )
- for ( int i 0 i lt length i )
- cout ltlt elements i ltlt " "
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- //
- // A B C D E F G H I J
61File selsort6.cpp
- // Shows version using STL iterators (only) as
arguments - include ltvectorgt
- include ltiostreamgt
- template lt class RandomAccessIterator gt
- void selectionSort( RandomAccessIterator start,
- RandomAccessIterator end )
- for ( RandomAccessIterator where start
where ! end where ) - RandomAccessIterator loc where, small
where - for ( RandomAccessIterator inner where
1 inner ! end inner ) - if ( inner lt loc )
- loc inner
- loc where
- where small
-
62File selsort6.cpp main()
- int main()
- vector lt char gt v
- for ( int i 10 i gt 0 i-- )
- v.push_back( 10 - i 65 )
- selectionSort( v.begin(), v.end() )
- for ( vector lt char gt iterator itr
v.begin() - itr ! v.end()
- itr )
- cout ltlt itr ltlt " "
- cout ltlt endl
- return 0
-
- // EXPECTED OUTPUT
- // A B C D E F G H I J
63Tip Regarding Typedefs
- One consequence of templates is that the names of
a fully qualified type may be quite long - For example
- itkImageltint, 3gt 3DIntImageType // Ugh!
might be a legal type
64Tip Regarding Typedefs
- You can create a shorthand version by using the
typedef keyword, so the code is only ugly once - typedef itkImageltint, 3gt 3DIntImageType //
Ugh! - 3DIntImageType myImage // Pretty!
65No Abstraction Penalty
- C is the language with the best facilities for
generic programming - Has better support for
- Class templates
- Function templates
- Operator overloading
- Polymorphism
- There is essentially no abstraction penalty at
runtime - So, feel free to give your code a lift