140 likes | 321 Views
A General Look at Type Programming in C++. Associated types (the idea) Let you associate a type with another type Given a type parameter, let you get the other type Let you control encapsulation of type information Typedefs + specialization (the mechanisms)
E N D
A General Look at Type Programming in C++ • Associated types (the idea) • Let you associate a type with another type • Given a type parameter, let you get the other type • Let you control encapsulation of type information • Typedefs + specialization (the mechanisms) • Let you give a known type another name • Let you name associated types consistently • Across user-defined/built-in const/non-const types • Traits (the technique) • Let you associate user-defined and built-in types • Let you provide consistent access across types
Basic Example: Associated Types (the Idea) • Want to swap the values of two locations in memory • Basic idea is simple • Declare a temporary variable • “Remember” value from location i1 aliases in temp • Copy value from location i2 aliases into location i1 aliases • Copy “remembered” value from temp into location i2 aliases • But, code to left won’t compile • How can we declare temp’s type? • How can we get T from I? // Based on Austern pp. 34 template <typename I> void swap (I i1, I i2) { T temp = *i1; *i1 = *i2; *i2 = temp; }
Typedef + Specialization (the Mechanisms) // Based on Austern, pp. 35 template <typename I> struct iterator_traits { typedef typename I::value_type value_type; }; template <typename T> struct iterator_traits<T*> { typedef T value_type; }; template <typename T> struct iterator_traits<const T*> { typedef T value_type; }; • Use specialization of a general traits struct template • Still have to parameterize struct iterator_traits with typename I • Can’t do full specialization since we’re using type parameter T • Gives us different versions of the iterator_traits struct • For user-defined, built-in types • For const, non-const types • Remember that the C++ compiler will select the most specific match
Traits (the Technique) • Given the iterator type I • Traits provide an interface to I‘s associated types • E.g., iterator_traits<I>::value_type • Let us obtain and use those types in template code • In general, let us go from containers to iterators to traits to associated types • Even within templates template <typename I> void swap (I i1, I i2) { iterator_traits<I>::value_type temp = *i1; *i1 = *i2; *i2 = temp; }
Goal Print the contents of different data sets First approach Use list and vector data stored in ranges Iterator for ostream lets us print to cout The copy algorithm Copies data ranges Get similar code 2 places Desired improvement Factor out common code How can we make this code more generic? What type info do we need to do that? #include <iostream> // For cout, manipulators. #include <list> // For lists. #include <vector> // For vectors. #include <iterator> // For ostream iterator. using namespace std; int main (int, char *[]) { list<char> l; l.push_back ('a'); l.push_back ('b'); l.push_back ('d'); cout << "list l contains: "; ostream_iterator<char> osic (cout, " "); copy (l.begin(), l.end(), osic); cout << endl; vector<int> v; v.push_back (1); v.push_back (2); v.push_back (4); cout << "vector v contains: "; ostream_iterator<int> osii (cout, " "); copy (v.begin(), v.end(), osii); cout << endl; return 0; } Another Type Programming Example
Use of copy algorithm Takes three iterators Two input iterators One ostream iterator Input iterator types Can we get those from the container? If so, how? Output iterator type Parameterized with char vs. int Can we get that parameterized type from the input iterators? If so, how? #include <iostream> // For cout, manipulators. #include <list> // For lists. #include <vector> // For vectors. #include <iterator> // For ostream iterator. using namespace std; int main (int, char *[]) { list<char> l; l.push_back ('a'); l.push_back ('b'); l.push_back ('d'); cout << "list l contains: "; ostream_iterator<char> osic (cout, " "); copy (l.begin(), l.end(), osic); cout << endl; vector<int> v; v.push_back (1); v.push_back (2); v.push_back (4); cout << "vector v contains: "; ostream_iterator<int> osii (cout, " "); copy (v.begin(), v.end(), osii); cout << endl; return 0; } Containers Iterators Traits Types
Containers Iterators (Types) template <class T, size_t N> struct block{ typedef T value_type; typedef value_type * pointer; typedef value_type & reference; typedef const_value_type* const_pointer; typedef const value_type & const_reference; typedef size_t size_type; …… typedef pointer iterator; typedef const_pointer const_iterator; …… }; From Austern, pp. 61 Every STL container must provide these types
Containers Iterators (Instances) template <class T, size_t N> struct block{ …… iterator begin() {return data;} iterator end() {return data+N;} const_iterator begin() const {return data;} const_iterator end() const {return data+N;} …… T data [N]; }; From Austern, pp. 61 Every STL container must produce iterators of the correct type (Factory Method Pattern)
Can now write a (somewhat) generic print_container function template Takes container type T as a template type parameter Takes const reference to container t as a function parameter Uses associated types Obtained from passed container types Uses typename where necessary Uses iterator accessors t.begin() and t.end() #include <iterator> #include <algorithm> using namespace std; template <typename T> void print_container (const T & t) { ostream_iterator <typename T::value_type> osi (cout, " "); copy (t.begin(), t.end(), osi); cout << endl; } Containers Iterators Traits Types
Program is now simpler Manipulates data, containers Leaves printing details to the generic print_container function template However, there is still one remaining limitation Cannot use for built-in arrays, or variables like double, or char[] We will fix this limitation next By changing to a range-based print function interface By declaring our own traits #include "print_container_T.h" #include <iostream> // For cout. #include <list> // For lists. #include <vector> // For vectors. using namespace std; int main (int, char *[]) { list<char> l; l.push_back ('a'); l.push_back ('b'); l.push_back ('d'); cout << "list l contains: "; print_container (l); vector<int> v; v.push_back (1); v.push_back (2); v.push_back (4); cout << "vector v contains: "; print_container (v); return 0; } How Making a Function Generic Can Help /* output is list l contains: a b d vector v contains: 1 2 4 */
Could have used pointer traits already provided by the STL But, it’s good to see the technique itself again So you can associate other types as needed Notice use of partial specialization and use of typename, again Notice separate partial specializations for const and non-const, again Now we have our associated types Can use in an even more generic print function template template <typename T> struct print_traits { typedef typename T::value_type value_type; }; // partial specialization for pointers template <typename T> struct print_traits<T *> { typedef T value_type; }; // partial specialization for const pointers template <typename T> struct print_traits<const T *> { typedef T value_type; }; Declaring Your Own Traits
Generic print function template Takes iterator type parameter Takes iterators as function parameters (define a range) Used typedef for convenience Here, as a kind of local type name declaration Avoid excessive use, but can aid coding style in some cases Applies generically to iterators Including const and non-const pointers to memory locations #include "print_T.h" #include <iterator> #include <algorithm> using namespace std; template <typename I> void print (I i, I j) { typedef typename print_traits<I>::value_type VTYPE; ostream_iterator<VTYPE> osi (cout, " "); copy (i, j, osi); cout << endl; } Using Your Traits in a Generic Function Template
Program is as simple as it was before But now has added capabilities Can print variables Can print arrays All using generic type programming in C++ #include "print_T.h" #include <iostream> // For cout, manipulators. #include <list> // For lists. #include <vector> // For vectors. #include <cstring> // For strlen. using namespace std; int main (int, char *[]) { list<char> l; l.push_back ('a'); l.push_back ('b'); l.push_back ('d'); cout << "list l contains: "; print (l.begin(), l.end()); vector<int> v; v.push_back (1); v.push_back (2); v.push_back (4); cout << "vector v contains: "; print (v.begin(), v.end ()); char * s = "hello, world!"; cout << "C-style string s contains: " << endl; print (s, s + strlen(s)); const double d = 3.141; cout << "Const double precision floating \n" " point number d contains: "; print (&d, (&d)+1); return 0; } How Generic Type Programming Has Helped /* output is list l contains: a b d vector v contains: 1 2 4 C-style string s contains: h e l l o , w o r l d ! Const double precision floating point number d contains: 3.141 */
Concluding Remarks • Associated types are a powerful idea in C++ • Let you use templates more effectively, easily • Let you make programs simpler, better encapsulated • Typedefs give a way to provide consistent type names • Let user-defined types declare associated types • Let you create type names for other purposes, convenience • Traits abstract away problems with associating types • Across user-defined, built-in, const, non-const types • Ensures consistency, makes types/algorithms more generic • The STL takes these ideas even further • Allows algorithms to be bound to specific iterator categories • For arbitrary reasons, like performance/correctness trade-off