Templates and the Quest for Generics - PowerPoint PPT Presentation

1 / 65
About This Presentation
Title:

Templates and the Quest for Generics

Description:

DT max( const DT& a, const DT& b ) { if ( a b ) return b; else. return a; ... i3 = max( i1, i2 ); ... it uses the template to create this function: ... – PowerPoint PPT presentation

Number of Views:24
Avg rating:3.0/5.0
Slides: 66
Provided by: walter115
Category:

less

Transcript and Presenter's Notes

Title: Templates and the Quest for Generics


1
Templates and the Quest for Generics
  • By Walter Maner

Based partly on presentations by Richard Chang _at_
UMBC, Templates Brian J. Higgs, C Class
Templates
2
What'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

3
What'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

4
What'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?

5
One 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

6
One 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

7
The 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

8
The 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

9
Templates 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

10
The 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
11
Class 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
12
DigressionOther 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

13
DigressionNontype 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

14
Nontype 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

15
Nontype 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

16
Polymorphism
  • 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

17
Function 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

18
Function 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

19
Function 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

20
max( ) 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

21
max( ) 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

22
max( ) 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

23
Calling 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

24
Can 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

25
Can 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

26
A 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

27
smartArray 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

28
Lifted smartArray Class
  • template ltclass Tgt
  • class smartArray
  • public
  • smartArray( int size 100 )
  • private
  • int _size
  • T _theData // array of any type

29
A
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
30
Parameterized 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?
31
Parameterized 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

32
Parameterized 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
33
Parameterized operator
  • template ltclass Tgt
  • T smartArrayltTgtoperator ( int index )
  • return _theData index

34
Using 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

35
Template .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

36
How 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

37
Templates 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

38
Templates 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

39
Templates 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( )
  • //

40
Templates 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
  • //

41
Writing 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

42
Lifting Code
  • Simple ?
  • Pointer-based ?
  • Typedef ?
  • Template ?
  • Range-based ?
  • Fully Generic STL-ready

43
File 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

44
File 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
45
File 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?
46
File 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?
47
File 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
  • //

48
File 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

49
File 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
  • //

50
File 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

51
File 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
  • //

52
File 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

53
File 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

54
File 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

55
File 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

56
File 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

57
File 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

58
File 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

59
File 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

60
File 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

61
File 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

62
File 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

63
Tip 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

64
Tip 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!

65
No 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
Write a Comment
User Comments (0)
About PowerShow.com