a few more thoughts on generic programming

22
CSE 332: Generic Programming and the C++ STL A Few More Thoughts on Generic Programming An abstraction technique for algorithms Argument types are kept as general as possible This is true both for iterator types and types they “point to” – E.g., vector<int>::iterator vs. list<char>::iterator Separates algorithm from container properties Stepping through a range vs. how each step is made Running to the end vs. how the end is determined Supports even non-STL “containers” like arrays, variables • E.g., via pointers as in the print function template from last lecture Results in high performance while keeping flexibility • Different algorithm implementations can be supported • Most efficient implementation that’s still correct is called • Uses traits-based dispatching technique we discussed earlier

Upload: zeus-bruce

Post on 02-Jan-2016

26 views

Category:

Documents


0 download

DESCRIPTION

A Few More Thoughts on Generic Programming. An abstraction technique for algorithms Argument types are kept as general as possible This is true both for iterator types and types they “point to” E.g., vector::iterator vs. list::iterator - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

A Few More Thoughts on Generic Programming

• An abstraction technique for algorithms– Argument types are kept as general as possible– This is true both for iterator types and types they “point to”– E.g., vector<int>::iterator vs. list<char>::iterator

• Separates algorithm from container properties– Stepping through a range vs. how each step is made– Running to the end vs. how the end is determined– Supports even non-STL “containers” like arrays, variables

• E.g., via pointers as in the print function template from last lecture

– Results in high performance while keeping flexibility• Different algorithm implementations can be supported• Most efficient implementation that’s still correct is called• Uses traits-based dispatching technique we discussed earlier

Page 2: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Generic Programming Mechanisms

• Basic interface polymorphism– Allows you to use different types in a container– Supported by template type parameterization

• Associated types give a new level of indirection– Let you obtain and use iterators generically– Supported by typedefs, traits (previous lecture)

• Generic algorithm dispatching (today)– Let you associate iterator/algorithm categories– Supported by traits and operator overloading

Page 3: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Trade-offs in Matching Algorithms and Iterators• Different algorithm implementations are

possible (and may perform differently)– Different iterator concepts may be

appropriate for each implementation– I.e., ++(++p) vs. p+=2 to move 2 positions

• Stronger concept (better performance)– Fewer types match the concept– Those that match offer more features → use

them for better performance

• Weaker concept (greater flexibility)– More types match the concept– Can only assume general features → may

have a cost in performance

• Goal– Selectively match different versions of

algorithms with each iterator– Dispatch the best performing algorithm that

each iterator supports

X

X

0 1 2 3 4

Page 4: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Idea: Associated Type for Iterator Category• Output iterator

typename iterator_traits<x>::iterator_category

• Input, forward, bidirectional, and random access iterators typename iterator_traits<x>::iterator_category

typename iterator_traits<x>::value_type

typename iterator_traits<x>::difference_type

typename iterator_traits<x>::pointer

typename iterator_traits<x>::reference

• Iterator category types: empty structs for type tags (note use of inheritance)struct output_iterator_tag {};

struct input_iterator_tag {};

struct forward_iterator_tag : public input_iterator_tag {};

struct bidirectional_iterator_tag :

public forward_iterator_tag {};

struct random_iterator_tag :

public bidirectional_iterator_tag {};

Page 5: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Technique: Dispatch Algorithms via Iterator Tags• Compiler directed

static dispatching– Different signatures

for different algorithm implementations (overloading)

– Use specialization to give traits to pointers

– Compile-time iterator type test is based on iterator category tags

– Links in the best implementation (after instantiating template)

// Based on Austern, pp. 38, 39

template <class Iter, class Distance>

void move (Iter i, Distance d,

forward_iterator_tag) {

while (d>0) {--d; ++i} // O(d)

}

template <class Iter, class Distance>

void move (Iter i, Distance d,

random_iterator_tag) {

i+=d; // O(1)

}

template <class Iter, class Distance>

void move (Iter i, Distance d) {

move (i, d,

iterator_traits<Iter>::

iterator_category());

}

tag (empty struct) type

explicit constructor call

Page 6: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Algorithms that use Tags (from g++ 3.3.3 STL)

• Finding a value in a rangefind locates the first occurrence of a value in a range

find_if locates first value in a range satisfying a predicate

find_end locates last occurrence of a sequence in a range

• Copying a range into another rangecopy copies values front to back to another range

copy_backward copies values back to front to another range

unique_copy eliminates consecutive duplicates during copy

• Reordering values within a rangereverse reverses the order of elements in a range

rotate shifts elements left one position, first wraps to last

partition puts values matching a predicate ahead of others

Page 7: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

find,find_if Algorithms with Input Iterators

• Sequential access using operator++• Time spent is linear in number of

positions searched• find_if replaces == with predicate

template<typename _InputIter, typename _Tp> inline _InputIter find (InputIter __first, _InputIter __last, const _Tp & __val, input_iterator_tag) { while (__first != __last && !(*__first == __val)) ++__first; return __first; }

template<typename _InputIter, typename _Predicate> inline _InputIter find_if (_InputIter __first, _InputIter __last,

_Predicate __pred, input_iterator_tag) { while (__first != __last && !__pred(*__first))

++__first; return __first; }

Page 8: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

find Algorithm with Random Access Iterators• Searches 4

positions each “trip”– Note distance, >>– Not clear at what

performance gain– Your mileage may

vary (YMMV) • But, demonstrates

an alternative way to implement find

• Random access version of find_if algorithm is similar– Replaces == with

a predicate functor• Good opportunity

for a code critique– What could you

change to improve readability/style?

– What changes might improve performance?

template<typename _RandomAccessIter, typename _Tp>RandomAccessIterfind (_RandomAccessIter __first, _RandomAccessIter __last, const _Tp & __val, random_access_iterator_tag) { typename iterator_traits<_RandomAccessIter>::difference_type __trip_count = (__last - __first) >> 2;

for ( ; __trip_count > 0 ; --__trip_count) { if (*__first == __val) return __first; ++__first;

if (*__first == __val) return __first; ++__first; if (*__first == __val) return __first; ++__first; if (*__first == __val) return __first; ++__first;

}

switch (__last - __first) { case 3:

if (*__first == __val) return __first; ++__first; case 2:

if (*__first == __val) return __first; ++__first; case 1:

if (*__first == __val) return __first; ++__first; case 0: default:

return __last; } }

Page 9: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

find_end Algorithm with Forward Iterators

• Number of comparisons done can be quadratic in range distance– At each step of while loop, do a search (which has another loop)– Pathological case: entire range filled with value we’re looking for

template<typename _ForwardIter1, typename _ForwardIter2>_ForwardIter1__find_end(_ForwardIter1 __first1, _ForwardIter1 __last1, _ForwardIter2 __first2, _ForwardIter2 __last2,

forward_iterator_tag, forward_iterator_tag) { if (__first2 == __last2) return __last1; else { _ForwardIter1 __result = __last1; while (1) { _ForwardIter1 __new_result

= search(__first1, __last1, __first2, __last2); if (__new_result == __last1) return __result;

else { __result = __new_result; __first1 = __new_result; ++__first1; } }

}}

Page 10: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

find_end Algorithm (Bidirectional Iterators)• Here, number of

comparisons is linear • Algorithm combines

(backward) search with (backward) advance function

• Allows it to avoid unnecessary repetition of forward searches

template<typename _BidirectionalIter1, typename _BidirectionalIter2>_BidirectionalIter1__find_end(_BidirectionalIter1 __first1, _BidirectionalIter1 __last1,

_BidirectionalIter2 __first2, _BidirectionalIter2 __last2,

bidirectional_iterator_tag, bidirectional_iterator_tag) {

// . . . concept requirements code (omitted) . . .

typedef reverse_iterator<_BidirectionalIter1> _RevIter1; typedef reverse_iterator<_BidirectionalIter2> _RevIter2; _RevIter1 __rlast1(__first1); _RevIter2 __rlast2(__first2); _RevIter1 __rresult = search(_RevIter1(__last1), __rlast1, _RevIter2(__last2), __rlast2);

if (__rresult == __rlast1) return __last1; else { _BidirectionalIter1 __result = __rresult.base(); advance(__result, -distance(__first2, __last2)); return __result; }}

Page 11: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

copy Algorithm (Input vs. Random Access)

• First version compares iterators

• Second version compares distance

• No reduction in number of comparisons or assignments made …

• … but each comparison may be faster

• BTW, note the use of a typedef

template<typename _InputIter, typename _OutputIter>inline _OutputIter__copy (_InputIter __first, _InputIter __last,

_OutputIter __result, input_iterator_tag) { for ( ; __first != __last; ++__result, ++__first) *__result = *__first; return __result;}

template<typename _RandomAccessIter, typename _OutputIter>inline _OutputIter__copy (_RandomAccessIter __first, _RandomAccessIter __last,

_OutputIter __result, random_access_iterator_tag) { typedef typename iterator_traits<_RandomAccessIter>::difference_type _Distance;

for (_Distance __n = __last - __first; __n > 0; --__n) { *__result = *__first; ++__first; ++__result; }

return __result;}

Page 12: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

copy_backward Algorithm (bidir vs. rand)• Similar to copy,

but both versions require operator--

• Similar issues to previous example

• Same number of comparisons and assignments is made …

• … but again each comparison may be faster

• Notice order in which decrement and dereference are done– Start of reverse

range is same as end of forward range

template<typename _BidirectionalIter1, typename _BidirectionalIter2>inline _BidirectionalIter2__copy_backward(_BidirectionalIter1 __first, _BidirectionalIter1 __last, _BidirectionalIter2 __result, bidirectional_iterator_tag) { while (__first != __last) *--__result = *--__last; return __result; }

template<typename _RandomAccessIter, typename _BidirectionalIter>inline _BidirectionalIter__copy_backward (_RandomAccessIter __first, _RandomAccessIter __last,

_BidirectionalIter __result, random_access_iterator_tag) {

typename iterator_traits<_RandomAccessIter>::difference_type __n; for (__n = __last - __first; __n > 0; --__n) *--__result = *--__last; return __result;}

Page 13: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

unique_copy Algorithm (output vs. forward)• Linear complexity in

both versions• First version

requires a persistent local variable

• Second version can just use iterators– Saves making an

extra copy of each value

• Good illustration – a forward iterator

aliases persistent memory

– an output iterator need not do so

• Again notice order of increment and dereference– A different reason

this time: why?

template<typename _InputIter, typename _OutputIter>_OutputIter__unique_copy (_InputIter __first, _InputIter __last,

_OutputIter __result, output_iterator_tag) {typename iterator_traits<_InputIter>::value_type __value = *__first; *__result = __value; while (++__first != __last)

if (!(__value == *__first)) { __value = *__first; *++__result = __value;}

return ++__result; }

template<typename _InputIter, typename _ForwardIter>_ForwardIter__unique_copy (_InputIter __first, _InputIter __last, _ForwardIter __result, forward_iterator_tag) { *__result = *__first; while (++__first != __last)

if (!(*__result == *__first)) *++__result = *__first;

return ++__result;}

Page 14: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

reverse Algorithm (bidir vs. rand)

• First version compares iterators for equivalence

• Second version compares where they are (address)

• Same order of complexity • But one fewer comparison in

random access version– How important is that?

• Observation: we’ve seen a lot of work on somewhat small performance improvements– But, remember that in many

cases, library code is called many times

– So, any optimization that can pay off even in aggregate is worth considering

template<typename _BidirectionalIter>void __reverse (_BidirectionalIter __first, _BidirectionalIter __last,

bidirectional_iterator_tag) { while (true) if (__first == __last || __first == --__last) return; else iter_swap (__first++, __last);}

template<typename _RandomAccessIter>void __reverse (_RandomAccessIter __first, _RandomAccessIter __last, random_access_iterator_tag) { while (__first < __last) iter_swap (__first++, --__last);}

Page 15: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

rotate Algorithm with Forward Iterators• Rotate algorithm

– Swaps ranges even if they’re of different sizes

– Takes valid ranges [first, last)[first, middle) and[middle, last)

– Puts the values that were in [middle, last) ahead of the values that were in [first, middle)

• Linear complexity• This version does

the most work

template<typename _ForwardIter>void__rotate (_ForwardIter __first, _ForwardIter __middle,

_ForwardIter __last, forward_iterator_tag) {

if ((__first == __middle) || (__last == __middle)) return;

_ForwardIter __first2 = __middle;

do { swap(*__first++, *__first2++);

if (__first == __middle) __middle = __first2;

} while (__first2 != __last);

__first2 = __middle;

while (__first2 != __last) { swap(*__first++, *__first2++); if (__first == __middle) __middle = __first2;

else if (__first2 == __last) __first2 = __middle;

}}

Page 16: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

rotate Algorithm with Bidirectional Iterators• Still linear complexity• This version does less

work than with forward iterators– Notice use of

bidirectional iterator tag to dispatch calls

• Random access version (next slide) does even less work– Again, notice use of

distance values, iterator position comparisons, arithmetic and math functions like gcd

– Illustrates how algorithm performance can be improved by using random access features

– E.g., can call swap_ranges if range sizes are same

template<typename _BidirectionalIter>void __rotate(_BidirectionalIter __first,

_BidirectionalIter __middle, _BidirectionalIter __last, bidirectional_iterator_tag) {

// . . . concept requirements (omitted) . . . if ((__first == __middle) || (__last == __middle))

return;

__reverse(__first, __middle, bidirectional_iterator_tag()); __reverse(__middle, __last, bidirectional_iterator_tag());

while (__first != __middle && __middle != __last) swap (*__first++, *--__last);

if (__first == __middle) { __reverse(__middle, __last, bidirectional_iterator_tag()); } else { __reverse(__first, __middle, bidirectional_iterator_tag()); }}

Page 17: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

rotate Algorithm with Random Access Iteratorstemplate<typename _RandomAccessIter>void __rotate (_RandomAccessIter __first, _RandomAccessIter __middle,

_RandomAccessIter __last, random_access_iterator_tag) { // . . . concept requirements (omitted) . . . if ((__first == __middle) || (__last == __middle)) return; typedef typename iterator_traits<_RandomAccessIter>::difference_type _Distance; typedef typename iterator_traits<_RandomAccessIter>::value_type _ValueType; _Distance __n = __last - __first; _Distance __k = __middle - __first; _Distance __l = __n - __k; if (__k == __l) {swap_ranges(__first, __middle, __middle); return;} _Distance __d = __gcd(__n, __k); for (_Distance __i = 0; __i < __d; __i++) { _ValueType __tmp = *__first; _RandomAccessIter __p = __first; if (__k < __l) { for (_Distance __j = 0; __j < __l/__d; __j++) { if (__p > __first + __l) {*__p = *(__p - __l); __p -= __l;} *__p = *(__p + __k); __p += __k; } } else {

for (_Distance __j = 0; __j < __k/__d - 1; __j ++) { if (__p < __last - __k) {*__p = *(__p + __k); __p += __k;} *__p = * (__p - __l); __p -= __l; } } *__p = __tmp; ++__first;

}}

Page 18: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

partition Algorithm with Forward Iterators• Linear

number of swaps

• First and next are moved forward

• Last marks the end of the range

template<typename _ForwardIter, typename _Predicate>_ForwardIter__partition (_ForwardIter __first, _ForwardIter __last, _Predicate __pred, forward_iterator_tag) {

if (__first == __last) return __first;

while (__pred(*__first)) if (++__first == __last) return __first;

_ForwardIter __next = __first;

while (++__next != __last) if (__pred(*__next)) { swap(*__first, *__next); ++__first; }

return __first;}

Page 19: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

partition Algorithm (Bidirectional Iterators)• Linear number

of swaps• First and last

are moved toward each other

• Doesn’t require an extra iterator

• Code style critique– What’s

missing?

template<typename _BidirectionalIter, typename _Predicate>_BidirectionalIter__partition(_BidirectionalIter __first, _BidirectionalIter __last, _Predicate __pred, bidirectional_iterator_tag) {

while (true) {

while (true) if (__first == __last) return __first; else if (__pred(*__first)) ++__first; else break;

--__last;

while (true) if (__first == __last) return __first; else if (!__pred(*__last)) --__last; else break;

iter_swap(__first, __last); ++__first; }}

Page 20: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

What these Examples Show

• Don’t have to give up efficiency– Avoid (small) v-tbl costs and (potentially larger) logic costs

• Dispatching decisions may slow compile-time, but improve run-time

– More importantly, can control algorithmic complexity• Which algorithm version can be used with which iterators

– Can also provide additional optimizations• Even if small, can add up in practice

• Can extend and customize without using inheritance– Templates offer compile-time type substitution– Including combinations of algorithms and iterators– Traits for pointers allow built-in types to be included as well

Page 21: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Concluding Remarks

• Summary of generic programming techniques– Can use templates to plug different types into containers– Can obtain and use associated types in your code– Can use typedefs to declare associated type names– Can use traits to provide associated types consistently – Can use partial specialization of traits for built-in types– Can use overloading to mix different iterators and algorithms

• Generic programming makes the STL more extensible– Can add your own containers, iterators, and algorithms– These can work seamlessly with existing STL code– Remember the goal of re-use: write less, do more– The techniques we’ve covered can help you do that

Page 22: A Few More Thoughts on Generic Programming

CSE 332: Generic Programming and the C++ STL

Next Week

• Terry and I will be away at the Real-Time Systems Symposium next week– I’ll be reachable via newsgroup and e-mail– Huang-Ming will be in the lab on Wednesday

• Tuesday 12/4/07 Topic: Advanced/Fundamental C++– Guest Lecturer: Huang-Ming

• Thursday 12/6/07 Topic: Design Patterns– Guest Lecturer: Prof. Kuhns

• No assigned reading– A good chance to catch up if needed