110 likes | 125 Views
Explore concepts, models, and templates in C++ programming, understanding requirements, concepts refinement, and template specialization. Learn how to override function, class, and struct templates for parameterized types with practical examples.
E N D
Review: Concepts and Models • Templates impose requirements on type parameters • Types that are plugged in must meet those requirements • Otherwise, the code won’t compile (and errors will say why) • The set of requirements imposed is called a concept • Any specific type that meets the requirements is a modelof that concept • What requirement(s) does the expression return first; impose? • What about while(first != last && *first != value) { ++first; } Iterator const T &
Concept Refinement Motivates Overriding • A concept C is a refinement of concept D if C imposes all of the requirements of D • Modeling and refinement satisfy three formal properties • Reflexivity: A concept refines itself • Containment: if T models C and C refines D then T models D • Transitivity: If C refines D then C refines any concept D refines • How can we override function, class, and struct templates for specific parameterized types? C0 transitivity C1 T1 T2 C2 containment T3 T4 can substitute, e.g., T3 for T1
Two examples Fully specialize a function template to print different types Partially specialize a struct template for iterator traits First example will refine output code several times Ending up with a specialized reusable function template Shows how to write fully specialized function templates Can only fully (not partially) specialize a function template Second example shows the iterator traits idiom Used by the STL to make pointers random access iterators Wraps a typedef for an associated type in a struct Allows partial specialization for specific pointer types Template Specialization: Overriding on Types
#include <iostream> using namespace std; int main (int, char *[]) { int i = 7; bool b = false; int * ip = &i; char * cp = "hello, world!"; void * vp = cp; cout << "i is " << i << endl; cout << "b is " << b << endl; cout << "ip is " << ip << endl; cout << "cp is " << cp << endl; cout << "vp is " << vp << endl; return 0; } Large number of types in C++ Each handled a bit differently For example, << with ostream Simply giving to cout gives i is 7 b is 0 ip is 0xfef69094 cp is hello, world! vp is 0x8048a3c We can improve on this! First Example: Printing Different Types
#include <iostream> using namespace std; int main (int, char *[]) { . . . cout << "i is " << i << endl; cout << "b is " << boolalpha << b << endl; cout << "ip is " << ip << endl; cout << "cp is " << cp << endl; cout << "vp is " << vp << endl; return 0; } First, fix how bool is output Use an iostream manipulator std::boolalpha prints true or false Instead of 1 or 0 Now we get i is 7 b is false ip is 0xfef2aa04 cp is hello, world! vp is 0x8048b0c But what about the pointers? Improve the Output for bool
#include <iostream> using namespace std; int main (int, char *[]) { . . . cout << "i is " << i << endl; cout << "b is " << boolalpha << b << endl; cout << "ip is " << ip << " (points to " << *ip << ")" << endl; cout << "cp is " << reinterpret_cast<void *> (cp) << " (points to \"" << cp << "\")" << endl; cout << "vp is " << vp << endl; return 0; } Now, fix pointers Use reinterpret_cast Convert char * to void * Use dereferencing Convert int * to int Now we have what we want i is 7 b is false ip is 0xfef23584 (points to 7) cp is 0x8048b70 (points to "hello, world!") vp is 0x8048b70 But, we don’t want to have to do all that over again the next time we want to print Improve the Output for Pointers
#include <iostream> using namespace std; #include "common_T.h" int main (int, char *[]) { int i = 7; bool b = false; int * ip = &i; char * cp = "hello, world!"; void * vp = cp; print(cout, "i is ", i); print(cout, "b is ", b); print(cout, "ip is ", ip); print(cout, "cp is ", cp); print(cout, "vp is ", vp); return 0; } Define print function template Consistent interface across types Just pass message, variable With this template we get i is 7 b is 0 ip is 0xfeea28f4 cp is hello, world! vp is 0x8048adc Right back where we started? Refactor the Code with a Function Template template <typename T> void print (ostream & os, const char * message, const T & t) { os << message << t << endl; }
typedef char * charptr; typedef int * intptr; template <> void print (ostream & os, const char * message, const bool & b) { os << message << std::boolalpha << b << endl; } template <> void print (ostream & os, const char * message, const charptr & s) { os << message << reinterpret_cast<void *> (s); if (s != 0) { os << " (points to \"" << s << "\")"; } os << endl; } template <> void print (ostream & os, const char * message, const intptr & ip) { os << message << ip; if (ip != 0) { os << " (points to " << *ip << ")"; } os << endl; } Specialize on individual types bool char * int * Notice the use of typedef With specialization, we get i is 7 b is false ip is 0xfeebf064 (points to 7) cp is 0x8048c30 (points to "hello, world!") vp is 0x8048c30 And, we can reuse the solution! Fully Specialize to Override Function Templates template <typename T> void print (ostream & os, const char * message, const T & t) { os << message << t << endl; }
Second Example: Iterator Traits struct input {}; // empty structs for type tags struct output {}; struct fwd : public input {}; // note inheritance struct bidir : public fwd {}; struct rand : public bidir {}; template <typename I> struct iterator_traits { ... typedef typename I::iterator_category iterator_category; }; template <typename T> struct iterator_traits<T*> { ... typedef rand iterator_category; }; template <typename T> struct iterator_traits<const T*> { ... typedef rand iterator_category; }; • Start with a few concrete types for category tags • E.g., empty structs for input, output, fwd, bidir, and rand categories • Those tags are used as associated types for iterators • Iterator category • Made available by partial specialization override for pointers (actually, random_access_iterator_tag)
Algorithm Dispatching via Category Tags // Based on Austern, pp. 38, 39 template <class Iter, class Distance> void move (Iter i, Distance d, fwd) { while (d>0) {--d; ++i;} // O(d) } template <class Iter, class Distance> void move (Iter i, Distance d, rand) { i += d; // O(1) } template <class Iter, class Distance> void move (Iter i, Distance d) { move (i, d, iterator_traits<Iter>:: iterator_category() ) } • Static dispatching • Implementations provide different signatures • Iterator type is evaluated at compile-time • Links to the best implementation • Notice how type tags are used concrete tag (empty struct) type concrete tag (empty struct) type explicit constructor call
The set of requirements that a class template or function template places on its parameterized types is called a concept in generic programming terminology Any type that meets those requirements is said to model the concept, and can be used in that template Concept refinement supports interface polymorphism Fully specialize function templates (and fully or partially specialize class and struct templates) to override template behaviors, etc. for specific types Summary of Templates and Specialization