Title: Templates and Generic Programming
1Templates and Generic Programming
2Motivating Problem
- Containers or data structures have behavior
that is independent of the type of object
contained within them. - The code that implements a linked list is
independent of whether integers or strings are
stored in the list. - Wed like to be able produce just one
implementation of a linked list and re-use that
implementation.
3Parameterized Types(template classes)
- A parameterized type is a user-defined type whos
properties are based in part upon parameters - For a container, the parameter is the type of
object to be stored inside the container. - ADA and C are two examples of languages which
provide support for parameterized types (Java
does not have this feature).
4Example of C Template Class
- template lttypename ElementTypegt
- class Vector
- ElementType array
- public
- explicit Vector(int size)
- ElementType operator(unsigned)
- const ElementType operator(unsigned) const
-
- The formal parameter ElementType can be used
anywhere in the definition of the class. - When the class is instantiated, an actual type
(the argument), for example int or double will be
bound to the formal parameter.
5An Alternative
- Use a generic object type (e.g., void from C,
or Object from Java). - class List
- void value
- List next
-
- main()
- List myList
- myList.insert((int) 42)
- x (int) myList.remove()
6Evaluation of Generic Object Containers
- Advantages
- Exactly one copy of source code and one copy of
object code no matter how many times the
container is re-used. - Can be polymorphic (different types stored
inside same container). - Disadvantage
- No static type checking when objects are inserted
or removed to/from the container
7Macros, the poor mans Parameterized Type
- A second alternative involves defining a macro.
- An expansion of the macro produces the desired
code for the container - define List(X) \
- class ListX \
- X value \
- ListX next \
-
- List(int) // expand the macro for X int
- main()
- Listint myList // notice how name is formed
- Listint.insert(42)
-
-
8Evaluation of Macros
- Advantage
- Requires no language support, can be used in any
language - Disadvantages
- Face it, this is a kludge, the syntax and use is
ugly and error prone. - No syntax checking of macros (until theyre
expanded). - Programmer must be careful to avoid expanding the
macro the same way twice (and getting
multiply-defined symbols).
9Template Terminology
- Template Definition
- Specifying the template, including its parameter
list and the definition of the class. - Template Instantiation
- as a noun, this term refers to the resulting type
(class) that is created by binding arguments to
all of the type parameters for the template - as a verb, this term refers to the process of
creating the instantiated template. - Template declaration
- rarely used. declares the name of the template
and the parameter list but does not specify the
class.
10The Template Instantiation Process
- Template instantiation is performed statically
(i.e., at compile time). - Every distinct set of arguments results in a
completely distinct instantiated class - Each member function is recreated and compiled
for each new type. - Some compilers may even (mistakenly?)
recreate/recompile member functions each time the
template is instantiated (even if the arguments
are the same!)
11Textual Substitution
- It is usually adequate to visualize template
instantiation as a textual expansion of the
template with parameters replaced by arguments
(like a macro). - A good compiler will minimize the redundant code
produced. - Theres more error checking with templates than
you could ever get with macros, however.
12The .h file gotcha
- Template definitions need to go inside .h files,
including definitions of all member functions. - a .cc file consisting of function member function
definitions for a template class will produce an
empty .o file! - the compiler does not use the template definition
until a type is actually instantiated. - your Vector.cc file cannot possibly instantiate
all the types of Vectors I might ultimately
decide to use in my application.
13Example Using Templates
- template lttypename Fst, typename Sndgt
- class Pair
- public
- Fst first
- Snd second
-
- main(void)
- Pairltint, chargt aPair // binds Fst to int,
Snd to char - aPair.first 42
- aPair.second hello world
- Pairltdouble, chargt anotherPair // a different
type! - anotherPair aPair // error! different types!
14Template Functions
- In addition to the parameterized-type feature,
C also supports functions with type parameters
(template functions). - Note technically, the member functions in a
template class are actually template functions. - template lttypename ElementTypegt
- VectorltElementTypegtVector(int sz) size(sz)
- array new ElementTypesize
15Useful Template functions
- template lttypename Tgt
- T min(const T x, const T y)
- if (y lt x) return y
- else return x
-
- template lttypename Tgt
- void swap(T x, T y)
- T t x
- x y
- y t
-
- These functions are in ltalgorithmgt
- The functions can be instantiated implicitly
(more on this later) or explicitly swapltintgt(a,
b)
16How to write operator
- Now, we can use template functions to simplify
the work of operator overloading. - never (almost never) write an operator member
function for your class. - instead always (almost always) write an
operator function. - template lttypename Tgt
- T operator(const T x, const T y)
- T t(x)
- t y
- return t
17Relationals
- Similarly, theres no reason to every write a !
member function, or a gt member function. - The standard include file ltfunctionalgt and
ltalgorithmgt defines - template lttypename Tgt
- bool operator!(const T x, const T y)
- return ! (x y)
-
- Greater than is defined using less than.
- You only need to write operatorlt and operator
for your classes.
18Templates in Templates
- The C standard allows a template class to have
template functions inside it. This feature is
very useful for type conversions - template lttypename ElementTypegt
- class Vector
- template lttypename OtherElementTypegt
- Vector(const VectorltOtherElementTypegt v)
- copy(v)
-
-
19Syntax for Member Templates
- template lttypename ElementTypegt
- template lttypename OtherElementTypegt
- void VectorltElementTypegtcopy(const
- VectorltOtherElementTypegt other)
- size other.getSize()
- array new ElementTypesize
- for (unsigned k 0 k lt size k 1)
- arrayk // other.arrayk oops!
- otherk
-
-
- NOTE you cannot access the private members of
the other type!
20Template Functions and Implicit Instantiation
- Recall that instantiation refers to the binding
of type arguments to the template parameters and
generating the actual C code. - With functions (only) in C the binding of
arguments to parameters can be implicit. - The basic rule is that the compiler must be able
to figure out what the arguments would be. - The technical rule is all implicitly determined
template parameters must appear in the parameter
list (function parameters) of the function.
21Implicit Instantiation Examples
- template ltclass Tgt
- T safe_dereference(T ptr)
- if (ptr 0) throw null_ptr_exception()
- else return ptr
-
- int p
- safe_derefence(p) 42 // OK, T is bound to int
- This template can be instantiated implicitly
because the type param (T) can easily be
determined from the function param (p). Since p
is int, then T must be int
22Implicit Instantiation Again
- template lttypename Tgt
- T nil(void)
- return 0
-
- int p nil() // sorry, no dice.
- int q nilltintgt() // OK like this, tho.
- By contrast, this re-implementation of the nil
template (as a function, rather than as a class)
cannot be instantiated implicitly. The return
type is not sufficient for the compiler to guess
T. - The reason for this rule is the same as the
reasons behind the function overloading rule.
Do you remember what that reason is?
23Mixing Implicit and Explicit
- It is possible to mix explicit and implicit
instantiation with templates. - template lttypename NewType, typename CurrTypegt
- NewType static_cast(CurrType p)
- return (NewType) p
-
- int p
- char q static_castltchargt(p)
- The implicit params must be at the end of the
parameter list (CurrType in this example). - Normal rules apply for implicit params.
24Non-Type Template Parameters
- Template parameters can be objects as well as
types. - The argument bound to these parameters must be a
constant expression of the appropriate type. - Including pointers to functions (or even pointers
to member functions) can be used.
25Non-Type Example
- template lttypename T, int sizegt
- class FixedArray
- T arraysize
- public
- T operator(unsigned k)
- if (k lt size) return arrayk
- else throw stdout_of_range()
-
-
- FixedArrayltint, 100gt x
26Default Arguments for Template Parameters
- There is no need for default arguments for
template functions because we have overloading - template lttypename X, typename Ygt
- void is_same(const X x, const Y y)
- cout ltlt x and y are different\n
-
- template lttypename Tgt
- void is_same(const T x, const T y)
- cout ltlt x and y are the same\n
27Defaults Arguments for Template Classes
- Since there is no overloading of classes,
template parameters can have default arguments in
template classes. - template lttypename T, int size100gt
- class FixedArray
- T arraysize
- public
- T operator(unsigned k)
- if (k lt size) return arrayk
- else throw stdout_of_range()
-
-
- FixedArrayltintgt x // 100 elements
28Generic Programming
- Objectives
- Produce a library of algorithms and data
structures. - Generic with respect to element type.
- Generic w.r.t container type.
- Predictable time complexity.
- Competitive performance with customized code.
29Representing a Data Structure
- The C and the Java library illustrate the two
leading ways to abstract a container. - Java uses the traditional abstract container
approach - C uses sequences of cursor objects.
- In C we use an auxiliary class object called an
iterator. Each container type should have a
companion iterator.
30Iterators Are Like Pointers
- Iterators are generalizations of pointers.
- The iterator for an array container is actually a
pointer (not a class). - The fundamental operations on a pointer are
- dereference
- increment
- compare for equivalence
- decrement
- add an integer (increment multiple)
- calculate difference of two iterators (subtract)
31Forward, Bidirectional, and Random Access
Iterators
- Not all data structures permit all pointer
operations to be implemented efficiently. - Linear time to add multiple in linked list.
- Linear time to decrement in singly-linked list.
- Some algorithms (e.g., binary search) may require
more capabilities than others (e.g., sort). - Binary search should be O(logN), but will only
happen if all pointer ops are implemented with
constant time. - When defining a data structure, you are
responsible for identifying whether your iterator
is forward, bidirectional or random access.
32Naming Conventions for Iterators
- To represent a container we use two iterators,
one that points to the first element and one
that points to the first position after the
last element. - Every container (i.e., data structure) must
define two functions one called begin() and one
called end(). - Ever container must define a nested type called
iterator (note lower case) for the iterator.
33A Simple Example
- For the simplistic Vector example, all we need is
to typedef ElementType to iterator. - typedef ElementType iterator
- typedef const ElementType const_iterator
- iterator begin(void) return array0
- iterator end(void) return
- arraynum_elements
- const_iterator begin(void) const return
- array0
- const iterator end(void) const return
- arraynum_elements
34Generic Bubble Sort
- template lttypename FIgt
- void bubbleSort(FI begin, FI end)
- if (begin end) return
- for (FI i begin i ! end i)
- FI j i
- FI k j
- k
- while (k ! end)
- if (j gt k) swap(j, k)
- j
- k
-
-
35Success?
- Note that the generic bubble sort can be invoked
on VectorltTgt for any T. - Also on T for any T or LinkedListltTgt for any T!
- Provided our optimizing compiler will inline all
the iterator operations (, , etc) - Should be just as fast as customized code!
(almost).
36Achieving Greater Customization
- Note that our sort function required that the
ElementType in the container define an
operatorlt(). - What if there is no natural lt operator?
- What if there could be more than one way to sort
the elements? - Wed like to be able to override the default
comparison and define our own. - Pointer to function would work, but would be WAY
SLOW!
37Function Objects
- We get inlining with templates because
instantiation is done at compile time. - If we want performance, we want the comparison
function to somehow be a template parameter (not
a function parameter). - What about an object type that has a known
method? - In C the convention is to use a class object
that defines the method called operator(). - Such an object is called a function object.
38Custom Sorting
- template lttypename FI, typename LTFuncgt
- void bubbleSort(FI begin, FI end, LTFunc f)
- if (begin end) return
- for (FI i begin i ! end i)
- FI j i
- FI k j
- k
- while (k ! end)
- if (f(j, k)) // j less than k
- swap(j, k)
-
- j
- k
-
-
39Achieving the Default with Overloading
- template lttypename Tgt
- class default_LT
- public
- bool operator()(const T x, const T y)
- return x lt y
-
-
- template lttypename FIgt
- void sort(FI b, FI e)
- sortltFI, Tgt(b, e, default_LTltTgt())
-
- Uh OH! How do we know what T is?
-
40Conventional tags for iterators
- Every iterator type should define the following
nested types (usually typedefs) - value_type
- iterator_category
- (there are other types as well, but these two are
the most useful). - Given an iterator FI we know that the element
type is FIvalue_type
41Using the nested types
- template lttypename FIgt
- void sort(FI b, FI e)
- typename FIvalue_type comp_func
- sort(b, e, comp_func) // implicit templ args
-
- The typename keyword was added to the language to
eliminate a source of ambiguity. - ideally, wed like the compiler to syntax check
template definitions (before the template is
actually instantiated). - FIvalue_type could be a member function, a
static variable or a nested type. Without
knowing what FI is, the compiler cannot syntax
check the template.
42Whoops! What about Pointers?
- One of the strengths of the C std library is
that all of the functions can be invoked on
arrays (the Java library for example does not
have this property). - We just broke this! Consider if FI is int and
the container is an array of ints. - Fortunately, there is another way to determine
the element type that will work with both
pointers (base types) and iterators (classes).
43Template Specialization
- Template specialization is a very powerful
feature that approximates overloading - except for classes rather than for functions.
- Consider the problem of defining a template class
that would return a representative value of a
any type. - T x representativeltTgt()
- For most classes, we can use the no-arg
constructor to get a representative object.
44General Case
- template lttypename Tgt
- class Representative
- public
- operator T(void) const
- return T()
-
-
- T x representativeltTgt()
- But what about ints? Or pointers?
- We can define specialized templates for these.
45Specialization for int
- / specialization for int /
- template ltgt
- class Representativeltintgt
- public
- operator int(void) const return 42
-
- Note the syntax, we are not defining a new
template, just defining a specialization of an
existing template. - Could do the same thing for double, short, etc.
- But what about the infinite number of pointer
types!
46Specialization for any pointer
- Specializations can be defined for broad classes
of template args. For example we can define a
specialization for RepresentativeltTgt for any
kind of T - template lttypename Tgt
- class RepresentativeltTgt
- public
- operator T(void) const
- void p operator new(sizeof(T))
- new (p) T(RepresentativeltTgt())
- return static_castltTgt(p)
-
47Iterator Traits
- Back to our iterator problem. Recall that we
wanted to be able to determine the element type
for any iterator argument (including base-type
pointers). - In addition to requiring all iterators to define
a nested type for value_type we will provide a
template class (and some specializations) called
iterator_traits
48- struct forward_iterator_tag
- struct bidirectional_iterator_tag
- struct random_access_iterator_tag
- template lttypename Igt
- class iterator_traits
- public
- typedef typename Ivalue_type value_type
- typedef typename Iiterator_category
- iterator_category
-
- template lttypename Tgt
- class iterator_traitsltTgt
- public
- typedef T value_type
- typedef random_access_iterator_tag
iterator_category -
49Using Iterator Traits
- Now we can write our default sort
- template lttypename FIgt
- void sort(FI b, FI e)
- typedef typename iterator_traitsltFIgtvalue_type
T - default_LTltTgt comp_func
- sort(b, e, comp_func) // implicit templ args
-
- Will work for any sequence, even if FI is a
pointer!
50Using iterator_category
- For some problems a faster solution may exist for
random access iterators than for forward
iterators. - We can use the iterator_category tag to
automatically select which algorithm to use. - We write a wrapper function that takes the
begin/end iterators (as normal). - We write two (overloaded) functions with the same
name. - One takes begin, end and an object of type
random_access_iterator_tag, - the other takes begin, end and an object of type
forward_iterator_tag
51- template lttypename Iteratorgt
- int length(Iterator b, Iterator e)
- typename iterator_traitsltIteratorgtiterator_cat
egory - tag
- real_length(b, e, tag)
-
- template lttypename Iteratorgt
- int real_length(Iterator b, Iterator e,
- random_access_iterator_tag)
- return e - b
-
-
- template lttypename Iteratorgt
- int real_length(Iterator b, Iterator e,
- forward_iterator_tag)
- int c 0
- while (b ! e)
- c 1
52Fast/Lazy Copy
- The C programming language (and associated
style) tends to result in making lots of copies
of objects. - For container objects
- Wastes memory
- Wastes time
- We only really need to copy the object to
- avoid delete problems
- make the objects appear independent (if one
changes, the other does not change).
53Lazy Copy for Containers
- With template containers, it is sufficient to use
reference counting for garbage determination. - for container C and element type T, you know that
CltTgt ! T and thus we cant have cycles. - The rest of the work simply is to ensure that we
copy at least as often as we have to. - e.g., at least when an object is about to be
changed and the reference count is gt1.
54Lazy copy for Vectors
- Copy constructor should not actually copy
elements - increment reference count and match pointers
- Mutating functions are
- push_back, pop_front, etc.
- operator (non-const)
- front, back (non-const)
- You have your option on
- begin/end
55Watch the Iterators!
- Iterators and lazy copy dont usually get along
very well together. - Vectorltintgt v // initialized somehow
- Vectorltintgtiterator p v.begin()
- Vectorltintgt w v // lazy copy
- p 42 // MUST NOT CHANGE w0!!!
- w0 17
- cout ltlt v0 ltlt endl // must print 42
- cout ltlt p ltlt endl // must print 42
- cout ltlt w0 ltlt endl // must print 17