1 / 22

A Few More Thoughts on Generic Programming

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

zeus-bruce
Download Presentation

A Few More Thoughts on Generic Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 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

  2. 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

  3. 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 0 1 2 3 4 X X

  4. 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 {};

  5. Technique: Dispatch Algorithms via Iterator Tags // 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()); } • 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) tag (empty struct) type explicit constructor call

  6. Algorithms that use Tags (from g++ 3.3.3 STL) • Finding a value in a range find 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 range copy 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 range reverse 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

  7. 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; } find,find_if Algorithms with Input Iterators 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; }

  8. 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> RandomAccessIter find (_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; } } find Algorithm with Random Access Iterators

  9. 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; } } } } find_end Algorithm with Forward Iterators

  10. 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; } } find_end Algorithm (Bidirectional Iterators)

  11. 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; } copy Algorithm (Input vs. Random Access)

  12. 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; } copy_backward Algorithm (bidir vs. rand)

  13. 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; } unique_copy Algorithm (output vs. forward)

  14. 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); } reverse Algorithm (bidir vs. rand)

  15. 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; } } rotate Algorithm with Forward Iterators

  16. 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()); } } rotate Algorithm with Bidirectional Iterators

  17. template<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; } } rotate Algorithm with Random Access Iterators

  18. 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; } partition Algorithm with Forward Iterators

  19. 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; } } partition Algorithm (Bidirectional Iterators)

  20. 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

  21. 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

  22. 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

More Related