Title: Generic Programming
1Generic Programming
2What Makes Software Reusable?
- Modularity
- Encapsulation
- Minimal interface
- Minimal requirements on inputs
3What is Generic Programming?
- Raising the level of abstraction.
- What do we do when we program?
- We write a sequence of statements.
- What is the level of abstraction?
- We write at the level of source code. That gets
translated directly into machine code, which is
less abstract.
4Source Code
- Normally, when we write source code, the
translation to machine code is direct. - int datadouble sum 0for (int i 0 i lt N
i) sum datai - The machine code depends only on the source code
as written. - What machine code comes out of the source code
does not depend on anything other than the source
code as written.
5- With generic programming, the level of
abstraction is higher. The source code is just a
pattern. - The actual machine code that comes out depends on
how the pattern is filled-in. - template lttypename TgtT sum 0for (int i 0
i lt N i) sum datai - Without knowing T, no machine code can be
generated from the above. - Summary
- With normal programming, we can know what the
assembly code should be just by looking at the
source. - With generic programming, the code is at a higher
level. Need to know other things, like the type
parameters.
6- So
- Generic programming is programming that focuses
on the algorithms and code at a higher level of
abstraction that normal programming. - It is one way of using features such as
templates, so is in some sense an application of
templates.
7Definitions
- Some definitions found in literature
- Programming with generic parameters
- Programming by abstracting from concrete types
- Programming with parameterized components
- Programming method based in finding the most
representation of efficient algorithms - First definition most commonly used.
- Avoids unnecessary code duplication.
8Motivation
- Great benefits with types, but code reuse is an
issue - int sqr(int i, int j) return ij
- double sqr(double i, double j) return ij
- The notion of sqr is the same but we must define
it twice because of types - Generic programming addresses this problem by
being able to write generic code that applies to
any type - T sqr(T i, T j) return ij
9Lifting
- Lifting is the first part of the process of
generic programming. It is answering the
question - What are the minimal requirements that my data
types need to fulfill for the algorithm to
operate correctly and efficiently?
10- Consider the following two definitions of
accumulation. - int accum(int array, int n) int result
0 for (int i 0 i lt n i) result
result arrayi return result - float accum(float array, int n) float
result 0 for (int i 0 i lt n i)
result result arrayi return
result - How can we lift this to create just one version?
- templatelttypename TgtT accum(T array, int n)
T result 0 for (int i 0 i lt n i)
result result arrayi return
result
11- Is our lifted code generic enough?
- templatelttypename TgtT accum(T array, int n)
T result 0 for (int i 0 i lt n i)
result result arrayi return
result - What about this?
- stdstring concatenate(stdstring array,
int n)
stdstring result "" for (int i 0 i lt
n i) result result arrayi
return result
12- Need to lift initialization
- templatelttypename TgtT accum(T array, int n)
T result T() for (int i 0 i lt n
i) result result arrayi
return result - What if we dont want to create a default
constructor? - T result Tzero
- Current code only works with pointers. Can we
generalize the concept of a container? - templatelttypename C, typename TgtT accum(const T
array, int n) T result 0 for (int i
0 i lt n i) result result
arrayi return result
13Concepts
- A concept is a set of requirements of a type. It
is less strict than a type. - A aa.func1()a.func2()a aa.data_member/
/ Cannot have more things not specified in the
definition of A. - Concept?
- T must have a function taking no arguments named
func1. - T must have a function taking no arguments named
func2. - T must have operator.
- T must have a data member named data_member.
- T must have a default constructor.
14- Consider this code. What concept does it define?
- templatelttypename I, typename TgtT accum(I start,
I end, T init) for (I cur start cur !
end cur
next(cur)) init init get(cur)
return init - Concept?
- T must have an additive operator .
- T must have an assignment operator.
- T must have a copy constructor.
- I must have an inequality operator !.
- I must have a copy constructor.
- I must have an assignment operator.
- I must have an operation next() that moves to the
next value in the sequence. - I must have an operation get() that returns the
current value (of type T).
15- Both of these classes satisfy a single concept,
but they are different types. What is it? - class A friend A operator(const A , const
A ) friend A operator-(const A , const A
) public A(const A )
void f1() private int i - class B friend B operator(const B , const
B ) friend B operator(const B , const B
) public B(const B )
void some_func() private double
x
16Modeling
- A given class may or may not satisfy the concept.
- If it does, we say that the class models the
concept. - A key point is that the set of classes that model
a concept is not fixed, nor is it known at coding
time.
17Refinement
- Similar to a subtype relationship, concepts can
also be related. A concept C2 is a refinement of
C1 if C2 imposes more requirements than C1. - A concept C2 is said to refine C1 if C2 has all
the functionality of C1, plus maybe more. - Example
- C1
- Must have assignment.
- Must have .
- Must have copy constructor.
- We say C2 refines C1 if C2 has everything in C1,
plus - Must have operator.
18- If a type D is a model of C2, and type B is a
model of C1, does that mean that B is a base
class of D? - No, since a concept is just a list of
requirements. For example, the data members could
be completely different.
19Traits
- Consider again our accumulate function
- templatelttypename TgtT accum(T array, int n)
T result T() for (int i 0 i lt n
i) result result arrayi
return result - What if we do something like?
- char a 125, 234, 9int sum accum(a, 3)
20- How to solve overflow?
- Need a different, bigger type to accumulate the
sum. - Hmuse a nested typedef, like
- templatelttypename TgtT accum(T array, int n)
typename Tresult_type result T() for
(int i 0 i lt n i) result result
arrayi return result - Does this work?
- How about for primitive types?
- How about for a class that is not under your
control? - Intrusive vs. Non-intrusive
21- Solution is to use a traits class.
- Works as a function mapping types to types and
other information. - templatelttypename TgtT accum(T array, int n)
typename TraitsltTgtresult_type
result T() for (int i 0 i
lt n i) result result arrayi
return result - So, Traitsltchargtresult_type should be int.
- How do we make it work?
- Traitsltchargtresult_type should be
int.Traitsltdoubleresult_type should be double.
22- Specialization
- template lttypename Tgt struct Traitstemplate ltgt
struct Traitsltchargt typedef int
result_typetemplate ltgt struct Traitsltdoublegt
typedef double result_type - Values can also be put in
- template ltgt struct Traitsltintgt static const
int zero 0 static int zero_func() return
0 template ltgt struct Traitsltdoublegt
static const double zero 0.0 // Error.
static double zero_func() return 0.0
23Policies
- In addition to traits, sometimes we want to
control behavior also - templatelttypename T, typename PgtT accum(T
array, int n) typename TraitsltTgtresult_typ
e result
TraitsltTgtzero() for (int i 0 i lt n
i) Paccum(result, arrayi)
return result
24- Now we can also do a multiplication
- templatelttypename T, typename PgtT accum(T
array, int n) typename TraitsltTgtresult_typ
e result
Tzero() for (int i 0 i lt n i)
Paccum(result, arrayi) return result - class Multiply template lttypename T1,
typename T2gt static void accum(T1 res, const
T2 val) return res val class
Sum template lttypename T1, typename T2gt
static void accum(T1 res, const T2 val)
return res val int a10
accumltint, Multiplygt(a, 10)accumltint,
Sumgt(a, 10)
25Templates as Functions
- What is a function (in general terms)?
- Something that maps one thing (or a set of
things) to another. - With a normal function, we map values to values.
Can we map a type to a type, or a type to an int? - f(int) ? doublef(char) ? int
- g(int) ? 5g(char) ? 1
- Templates can be used to do this.
26- Use specialization
- template lttypename Tgt struct ftemplate ltgt
struct fltintgt typedef double type
static const int integer 5template ltgt
struct fltchargt typdef int type static
const int integer 1 - fltintgttype ? doublefltchargttype ?
intfltintgtinteger ? 5fltchargtinteger ? 1
27Intrusive vs. Non-Intrusive
- When writing generic code, and libraries, we are
often given the choice between requiring that the
client code be modified, and working with
unmodified client code. - For example, lets say we want to want to put
objects of type MyClass into a linked list.
Options are - Create a Link struct that points to objects of
type MyClass. - struct Link Link next, prev MyClass
obj - Add links to MyClass
- class MyClass MyClass next,
prev - Which is better?
28- Intrusive
- Tends to be faster.
- Tends to be simpler.
- Okay for situations like in-house development,
etc. - Non-intrusive
- Tends to be slower.
- Tends to be more complicated.
- But can work with classes that you do not
control, and requires no code modification. - The Link class is type-dependent, not generic.
Generic programming is about writing type-safe,
non-intrusive, reusable code.
29Functors
- Suppose we are writing a sorting function. First,
we want to make it work with any type. - templatelttypename Tgtvoid sort(T array, int
len) - Now, we want to make it sort in any order. How do
we do that? - With a function pointer
- template lttypename Tgtvoid sort(T array, int
len, int (cmp)(const T , const T ))
30- With a functor using subtype polymorphism.
- template lttypename Tgtstruct Compare
virtual bool exec(const T , const T )
const 0 template
lttypename Tgtvoid sort(T array, int len,
const CompareltTgt functor)template
lttypename Tgtstruct MyCompare public CompareltTgt
virtual bool exec(const T t1,
const T t2) const
return t1 gt t2 int main() MyClass
array5 MyCompareltMyClassgt cmp
sort(array, 5, cmp) - Can this be inlined?
31- With a functor using parametric polymorphism.
- struct MyCompare inline bool
operator()(const char s1,
const char s2) const return strcmp(s1,
s2) lt 0 template lttypename T,
typename Cgtvoid sort(T array, int len, const C
cmp) int main() char array5
sort(array, 5, MyCompare()) - Can this be inlined?
32Genericity and Polymorphism
- One view of polymorphism is
- Ability of code to work with different types
- Generic parameters and subtyping
- Both mechanisms for accomplishing polymorphism
- Both support programming by abstracting from
concrete types - Polymorphism using generic types - parametric
polymorphism - Polymorphism using subtypes - subtype polymorphism
33- When we want a piece of code to be reusable, we
are usually faced with two choices. - Define a base class. Make the actual type derive
from it (subtype) - struct Base virtual void f() struct MyClass
public Base virtual void f() void
doit(Base obj) obj-gtf() - Define a concept. Create generic code based on
it. - template lttypename Tgtvoid doit(T obj)
obj-gtf() - Subtype polymorphism can cope with multiple
types, easier to debug. - Parametric can be inlined, be more flexible.
34STL view of Generic Programming
Generic programming is a subdiscipline of
computer science that deals with finding abstract
representations of efficient algorithms, data
structures, and other software concepts, and with
their systematic organization
- Goal is to find the most abstract representations
of efficient algorithms - No loss in efficiency
35Steps for Generic Programming
- Identify useful tasks.
- Find their generic representation.
- Derive a minimal set of requirements that allow
these algorithms to run. - Construct a framework (library) based on these.
36Standard Template Library
- A part of the Standard C Library
- Tries to separate algorithms and data structures.
- Containers
- Iterators
- Algorithms
- Functors
- Also provides performance guarantees
37Generic Programming Example
- double sum(double a, int n) double s 0
for (int i 0 i lt n i) s s
arrayi return s - Requirements?
- Elements of type double.
- Elements in an array.
38- template lttypename TgtT sum(T array, int n)
T s 0 for (int i 0 i lt n i)
s s arrayi return s - Requirements
- Elements in an array.
- Elements must support addition.
- Elements must support conversion from int.
- 0 is zero.
- Elements support assignment.
39- template ltclass RandomAccessIter, class TgtT
sum(RandomAccessIter iter, int n, T s) for
(int i 0 i lt n i) s s iteri
return s - Requirements
- Iterator is indexable.
40- template ltclass InputIter, class TgtT
sum(InputIter start, int n, T s) for (int i
0 i lt n i) s s start
return s - Requirements
- Iterator has and .
- Can determine the size of the set beforehand.
- Consider a stream, where you dont know how big
it is.
41- template ltclass InputIter, class TgtT
sum(InputIter begin, InputIter end, T s)
while (begin ! end) s s begin
return s - Requirements
- Iterator has !, , and .
42Requirements
- When in the design phase, be aware that more
requirements does not necessarily mean harder
requirements. - Which is harder, a constant time index operator
, or and ? - Are there data structures that can support the
index operator but not and ?
43Iterators
- What do want to do with an interator?
- Advance it. ()
- Go backwards. (--)
- Write to it. (it 1)
- Read from it. (i it)
- Index it. (iti)
- What concepts are there?
- Input Iterator
- Output Iterator
- Forward Iterator
- Bidirectional Iterator
- Random Access Iterator
- Basic idea is that of a pointer.
- Similar to visitor pattern.
44Input Iterator
- Input only
- Can be advanced
- Single pass, can read only once.
- To support streams.
- What can we do with this?
- Search/find
- Examples?
45Output Iterator
- Can be used to write.
- Cannot be used to read.
- Examples?
- What can we do with this?
46Forward Iterator
- Support forward direction.
- Supports multiple passes.
- Iterator can be copied.
- MyIterator it1 , it2 it1it1it2it1
it2 // True? - What can we do with this?
47Bidirectional Iterators
- Can go both directions.
- What can we do with this?
48Random Access
- Can index.
- Can compute distance.
- What can we do with this?
- Why need a separate concept? Why not just
implement random access with bidirectional? - Constant amortized time.
- Whats the difference between amortized time and
average time?
49Hierarchy of Iterator Concepts
Input Iterator
Output Iterator
Forward Iterator
Bidirectional Iterator
Random Access Iterator
50Iterator Adapters
- Reverse iterators
- Swap forward and backward directions.
- Insert iterators
- Transform an assignment into an insertion.
51Examples
- Binary tree
- Forward, bidirectional, random access
- Hash table
52Const Iterators
- What do you do if the container is constant?
53Validity
- What happens if an iterator is pointing to an
element, and another element is inserted into the
container?
54Associated Types
55Temporary Variables (Value Types)
- How do we create a temporary variable?
- template ltclass InputItergt??? sum_nonempty(InputI
ter first, InputIter
last) ??? result first for (
first ! last first) result
first - Suppose C had an operator which gave the type
of an expression - int itypeof(i) j // int itypeof(i) ip //
int ip - typeof(first) result first
56Nested Types
- struct MyIterator typedef Value
value_type - How do we create a temporary variable now?
- template ltclass InputItergt??? sum_nonempty(InputI
ter first,
InputIter last) ??? result first
for ( first ! last first) result
first
57Pointers
- What about for pointers?
- template ltclass InputItergtInputItervalue_types
um_nonempty(InputIter first, InputIter last)
InputItervalue_type result first for
( first ! last first) result
first - What will happen when you try to compile this?
- Solution?
58Iterator Traits
- Sample usage
- template ltclass InputItergtiterator_traitsltInputIt
ergtvalue_typesum_nonempty(InputIter first,
InputIter last) iterator_traitsltInputItergt
value_type result
first for ( first ! last first)
result first - How to define the iterator_traits class? Remember
that it is really mainly to work around the
problems of using this if it is a pointer class - ITvalue_type
59Iterator Traits
- General for the common case, specialized for
pointers - template lttypename Itergtstruct iterator_traits
typedef typename Itervalue_type
value_type - template lttypename Tgtstruct iterator_traitsltT gt
typedef T value_type - Const iterators?
- template lttypename Tgtstruct iterator_traitsltconst
T gt typedef T value_type - Other traits?
- Difference type
- Reference type
60Iterator Tags
- Suppose we want to be able to initialize a value
type to zero? - iterator_traitsltTgtvalue_type t
iterator_traitsltTgtzero - What should zero be?
61Advance
- We often need to advance an iterator n times. How
should it be implemented? - template ltclass InputIter, class Distancegtvoid
advance_II(InputIter i, Distance n) for (
n gt 0 --n, i)template ltclass
BidirectionalIter,
class Distancegtvoid advance_BI(BidirectionalIter
i, Distance n)
for ( n ! 0 n gt 0 ? (--n,
i) (n, --i))template ltclass
RandomAccessIter,
class Distancegtvoid advance_RAI(RandomAccessIter
i, Distance n)
i n - How would you make the first version safer?
62Selecting the Right One
- Suppose you are trying to use this in a templated
function - template ltgtvoid foo(Iter i, ) advance(i,
3) - How do you get the right one instantiated?
- Virtual functions?
- Member function?
- Overloading?
63Use Overloading
- Overload advance functions
- template ltclass InputIter, class Distancegtvoid
advance(InputIter i, Distance n,
input_iterator_tag) for ( n gt 0
--n, i)template ltclass RandomAccessIter,
class Distancegtvoid
advance (RandomAccessIter i, Distance n,
random_access_iterator_tag) i
n - How do we use it?
- template ltclass Iter, class Dgtinline void
advance(Iter I, D d) advance(I, n, ???)
64Functors
65Generic Algorithms
- How would you make a find function general?
- template lttypename InputItergtInputIteratorfind_i
f(InputIter first, InputIter last) while
(first ! last !???(first)) first
return first
66Generic Algorithms
- Function pointer
- template lttypename InputItergtInputIteratorfind_i
f(InputIter first, InputIter last,
bool (pred)()) while (first
! last !(pred)(first)) first
return first - Disadvantages?
- No state
- No inlining
67Generic Algorithms
- Nested type
- template lttypename InputItergtInputIteratorfind_i
f(InputIter first, InputIter last) while
(first ! last
!traitsltInputItergtpred(first))
first return first - Disadvantages?
- Cannot vary
- No state
68Generic Algorithms
- Function objects
- template lttypename InputIter, typename
PredgtInputIteratorfind_if(InputIter first,
InputIter last,
Pred pred) while (first ! last !
pred(first)) first return
first - Disadvantages?
- Ctor cost.
- A bit of a pain.
69Example
- Suppose you had
- stdvectorltstringgt vfind_if(v.begin(),
v.end(), / is hello /)find_if(v.begin(),
v.end(), / is
goodbye /) - How would you implement this?
70- class pred public pred(const
stdstring s) str(s) bool
operator()(const string s) return
s str private const
stdstring strint main()
vectorltstringgt v vectorltstringgtiterator
it it find_if(v.begin(), v.end(),
pred("hello")) if
(it ! v.end())
71Function Object Concepts
- Generator, Unary Function, Binary Function
- Predicate, Binary Predicate
- Strict Weak Order
- irreflexive
- antisymmetric
- transitive equivalence
- Does lt define a strict weak order?
72Orderings
- Partial order
- Reflexive
- Antisymmetric
- Transitive
- Examples?
- Total order
- All are comparable
- Weak order
- Complete
- Transitive
- Strict weak order
- Equivalence is transitive
73Adaptable Function Objects
- For simplicity, not all function objects need to
have type traits. - If they do, they are adaptable function objects.
74Function Object Adapters
- find_if(first, last, not1(even()))
- Also have composition, but they are not standard.
75Containers
76Container
77Forward Container
- Definite ordering.
- Models?
- What would not fit?
78Reversible Container
- Bidirectional iterators.
- Models
- vector
- list
- set
79Random Access Container
- Indexable
- Models
- vector
- deque
80Sequence
- Add insert/delete to Forward Container.
- Models
- vector
- deque
- list
81Front Insertion Sequence
- Possible to insert at front in amortized constant
time. - Models?
- list
- deque
82Associative Containers
- Supports efficient retrieval of elements via a
key. - Is an associative container a refinement of
sequence? - Models
- set, multiset, map, multimap
83Unique Associative Container
- Each key is unique.
- Models
- set, map
- What does unique mean?
- Whats the size of s?
- setltint, CompareOpgt ss.insert(2)s.insert(3)
- struct CompareOp bool operator()(int i1, i2)
return i1/2 lt i2/2
84Multiple Associative Container
- Can have multiple keys the same.
- Models
- multiset
- multimap
- Whats the size of s?
- multisetltint, CompareOpgt ss.insert(2)s.insert(
3)s.insert(3)
85Simple Associative Container
- Just keys
- Models
- set
- multiset
86Pair Associative Container
- Associates a key with some other object.
Basically a lookup table. - Models
- map
- multimap
87Sorted Associative Container
- Guarantees log operations.
- Models
- set
- map
- multiset
- multimap
88vector
- A smart array, basically.
- Operations at the end are guaranteed amort.
constant time. - Iterators are invalidated when you append.
- Example
- vectorltstringgt vv.reserve(5)v.push_back(straw
berry)v.push_back(fields)v.at(1000000000)
// Throws.v1000000000 // Seg faults. - Implementation?
89deque
- Double-ended queue. Operations are fast at either
end. - Iterators are invalidated.
- Implementation?
90list
- Doubly-linked list.
- Iterators are not invalidated.
- When you insert, you get an iterator that you can
later use to remove the element. - Can splice.
91set
- A set of things.
- Iterators are not invalidated.
- Implementation?
92set
- What are the sizes of set1 and set2?
- struct Cmp1 bool operator()(Obj o1, Obj
o2) return o1 lt o2 struct Cmp2
bool operator()(Obj o1, Obj o2)
return o1-gturl lt o2-gturl setltObj , Cmp1gt
set1setltObj , Cmp2gt set2Obj obj1(url),
obj2(url)set1.insert(obj1)
set1.insert(obj2)set2.insert(obj1)
set2.insert(obj2)
93map
- Maps keys to values. A lookup table.
- Iterators not invalidated.
- Example
- mapltstring, floatgt stocksstocksIBM
30.0it stocks.insert(make_pair(MSFT,
30.0)) - How do you support looking up an object via two
different keys?
94map
- Example
- mapltstring, Obj gt name2objmapltint, Obj gt
id2objvoid insert(Obj o) o-gtname_it
name2obj.insert(make_pair(o-gtname, o))
o-gtid_it id2obj.insert(make_pair(o-gtid,
o))void remove(Obj o)
name2obj.remove(o-gtname_it)
id2obj.remove(o-gtid_it) - How do you make the above thread safe?
95Misc
- multiset, multimap
- stack, queue, priority_queue
- No hash-table-based structures.
96Algorithms
97Algorithms
- Generic programming approach is to separate data
structures from the algorithms that operate on
data structures. - Does this seem like a good approach? Workable?
- R-B trees.
- STL provides a set of algorithms.
- find, for_each, reverse, sort, min, max, etc.
98string
- Pretty much as expected. Use c_str() to get a
const char . - How fast is this?
- string sfor (int i 0 i lt N i) s
a - How to make it faster?
- string s s.reserve(N)for (int i 0 i lt N
i) s a
99I/O
100Basic Classes
- istream, ostream
- fstream, stringstream
101Global Streams
- cin (istream)
- cout (ostream)
- cerr (ostream)
- unbuffered
- clog (ostream)
- buffered
102Stream Operators
- ltlt and gtgt
- cout ltlt a ltlt b
- cin gtgt a gtgt b
- Which is more readable?
- cout ltlt ( ltlt x0 ltlt , ltlt y0 ) ltlt X ltlt
( ltlt x1 ltlt , ltlt y1 ltlt ) ltlt endl - printf((f, f) X (f, f)\n x0, y0, x1, y1)
103Manipulators
- Special objects that you insert into the output
or input stream. - cout ltlt a ltlt endl
- cin gtgt ws gtgt a