Download presentation
Presentation is loading. Please wait.
Published byJohnathan Terry Modified over 9 years ago
1
Understanding C++ Templates CNS 3370 Copyright 2006, Fresh Sources, Inc.
2
Agenda Executive Overview Template Parameters Function Template Issues Template Specialization Member Templates Template Idioms
3
Executive Overview Generic Programming Compile-time Code Generation Implicit Interfaces Template Terminology
4
Generic Programming Writing type-transparent code –Not a new idea –Object mega-hierarchy; void* in C C++ prefers type-safe generics –Based on innovations from ML and Ada –Statically-checked –Allows efficient use of built-in types
5
Compile-time Code Generation Separate code versions are automatically generated On-demand code generation –Implicit and explicit Compile-time programming –Selective code generation Potential for ODR Violations –Smart linkers solve that Potential for Code Bloat template class Stack { T* data; size_t count; public: void push(const T& t); // etc. }; // Explicit instantiation Stack s;
6
What Gets Generated? If using a function template, the requested function is instantiated If using a class template, the requested version of the class definition is instantiated –For the compiler’s use (just like regular classes) –But only the member functions actually used are generated Usually :-)
7
Instantiation On Demand class X { public: void f() {} }; class Y { public: void g() {} }; template class Z { T t; public: void a() { t.f(); } void b() { t.g(); } }; int main() { Z zx; zx.a(); // Z ::b() not attempted Z zy; zy.b(); // Z ::a() not attempted } Question: When are the names f and g looked up by the compiler?
8
Where Should Template Code Reside? Totally in header files The template code must be available during instantiation –This is the Inclusion Model of template compilation
9
Implicit Interfaces aka “Duck Typing” –If it quacks like a duck, … (fits the bill :-) The status quo for dynamically-typed languages –Perl, Python, PHP, Ruby… C++ statically verifies that generic types support required operations template const T& min(const T& a, const T& b) { return (a < b) ? a : b; } … min(i, j) // T deduced
10
Template Terminology A First Look Template Parameter –Names inside <>’s after the template keyword ( ) Template Argument –Names inside <>’s in a specialization ( ) Template Specialization –What results when you give a template some arguments (a class, function, or another template) (Stack ) –A specialization may or may not be an instantiation Instantiation –The class or function generated for a given complete set of template arguments –An instantiation is always a specialization
11
Template Executive Summary Templates are instructions for compile-time code generation They enable type-safe, type-transparent code Template parameters constitute implicit interfaces –“Duck Typing” –“Compile-time polymorphism” Classic OOP uses explicit interfaces and runtime polymorphism –Templates and OOP complement each other
12
Sample Programs Template version of Programs 2 (Pool) and 3 (Bits)
13
Template Parameters 3 Kinds… Type parameters –the most common (vector ) Non-type –compile-time integral values (bitset, for example) Templates –“template template parameters”
14
Type Parameters The original motivation for templates –“Generic Programming” Container logic is independent of its containee’s type Containers of “T”: –template // T is a type parm class vector { T* data; … }; –vector v;// int is a type argument
15
Non-type Template Parameters Must be compile-time constant values –usually integral expressions –can also be addresses of static objects or functions –should probably be called “value parameters” Often used to place member arrays on the runtime stack –like with bitset b; –see next slide Often used in Template Metaprogramming –compile-time expressions governing code generation
16
Using std::bitset
17
Bitset Implementation (from STLPort) template struct _Base_bitset { typedef unsigned long _WordT; _WordT _M_w[_Nw]; // Can go on stack... }; template class bitset : public _Base_bitset {... };
18
Default Template Arguments Like default function arguments –if missing, the defaults are supplied –only allowed in class templates template class FixedStack { T data[N]; … }; FixedStack<> s1; // = FixedStack s2; // =
19
The vector Container Declaration template > class vector; Note how the second parameter uses the first Note the space between the ‘>’s –Will be fixed in C++0x
20
Template Template Parameters Templates are not types! –They are instructions for generating types –“Meta-types”, if you will If you plan on using a template parameter itself as a template, the compiler needs to know –otherwise it won’t let you do template things with it –You usually don’t use a name for its parameters Examples follow
21
// A simple, expandable sequence (like vector) template class Array { enum { INIT = 10 }; T* data; size_t capacity; size_t count; public: Array() { count = 0; data = new T[capacity = INIT]; } ~Array() { delete [] data; } void push_back(const T& t) {...} void pop_back() {...} T* begin() { return data; } T* end() { return data + count; } }; A Simple Expandable Sequence (will be used as a template argument)
22
template class Seq> class Container { Seq seq; public: void append(const T& t) { seq.push_back(t); } T* begin() { return seq.begin(); } T* end() { return seq.end(); } }; int main() { Container container; // Pass template container.append(1); container.append(2); int* p = container.begin(); while(p != container.end()) cout << *p++ << endl; } Passing Array as a template argument
23
Function Template Issues Type Deduction of Arguments Function template overloading Partial Ordering of Function Templates
24
Type Deduction in Function Templates Under most circumstances, the compiler deduces type parameters from the arguments in the call –the corresponding specialization is instantiated automatically You can use a fully-qualified call syntax if you want to: int x = min (a, b); // vs. min(a, b); Sometimes you have to: –when the arguments are different types ( min(1.0, 2) ) –when the template argument is a return type, and therefore cannot be deduced by the arguments –Example on next two slides
25
String Conversion Function Templates // StringConv.h #include template T fromString(const std::string& s) { std::istringstream is(s); T t; is >> t; return t; } template std::string toString(const T& t) { std::ostringstream s; s << t; return s.str(); }
26
#include #include "StringConv.h" using namespace std; int main() { // Implicit Type Deduction int i = 1234; cout << "i == \"" << toString(i) << "\"" << endl; float x = 567.89; cout << "x == \"" << toString(x) << "\"" << endl; complex c(1.0, 2.0); cout << "c == \"" << toString(c) << "\"" << endl; cout << endl; // Explicit Function Template Specialization i = fromString (string("1234")); cout << "i == " << i << endl; x = fromString (string("567.89")); cout << "x == " << x << endl; c = fromString >(string("(1.0,2.0)")); cout << "c == " << c << endl; }
27
Another Example implicit_cast Makes a legal implicit conversion visible in code Must specify return type (U) –Can’t be deduced (s = implicit_cast (x);) Can omit T –But only because we put it last in the parameter list –Couldn’t say implicit_cast template U implicit_cast(const T& t) { return t; }
28
Function Template Overloading You can define multiple functions and function templates with the same name The “best match” will be used –A regular function is preferred over a template All other things being equal –Can force using the template with “<>” You can also overload a function template by having a different number of template parameters
29
template const T& min(const T& a, const T& b) { return (a < b) ? a : b; } const char* min(const char* a, const char* b) { return (strcmp(a, b) < 0) ? a : b; } double min(double x, double y) { return (x < y) ? x : y; } int main() { const char *s2 = "say \"Ni-!\"", *s1 = "knights who"; cout << min(1, 2) << endl; // 1: 1 (template) cout << min(1.0, 2.0) << endl; // 2: 1 (double) cout << min(1, 2.0) << endl; // 3: 1 (double) cout << min(s1, s2) << endl; // 4: "knights who" // (const char*) cout (s1, s2) << endl; // 5: say "Ni-!" // (template) }
30
template void f(T) { cout << "T" << endl; } template void f(T*) { cout << "T*" << endl; } template void f(const T*) { cout << "const T*" << endl; } int main() { f(0); // T int i = 0; f(&i); // T* const int j = 0; f(&j); // const T* } Partial Ordering of Function Templates
31
Things to Remember… About Function Templates Arguments that can be deduced will be –The rest you must provide –Put those first in the argument list so the others can be left to deduction Standard Conversions do not apply when using unqualified calls to function templates –Arguments must match parameters exactly –Except const/volatile adornments are okay Function Templates can be overloaded –The “best match” is used
32
Template Specialization A template by nature is a generalization It becomes specialized for a particular use when the actual template arguments are provided A particular instantiation is therefore a specialization But there is another type of “specialization”…
33
Explicit Specialization What if you want special “one-off” behavior for certain combinations of template arguments? You can provide custom code for such cases –both full or partial specializations for class templates Meaning all or some of the template arguments are specified –the compiler will use your explicit versions instead of what the “primary template” would have instantiated –You are writing the “instantiation” Full specialization uses the template<> syntax
34
A Full Class Specialization Example // A Primary Template template class Foo { public: void f(); void g(); }; template void Foo ::f() { cout << "f using primary template\n"; } template void Foo ::g() { cout << "g using primary template\n"; }
35
Full Class Specialization Example // The Full Specialization template<> class Foo { public: void f(); void g(); }; // NOTE: No template keyword here // Also: These go in a.cpp void Foo ::f() { cout << "f using int specialization\n"; } void Foo ::g() { cout << "g using int specialization\n"; }
36
Full Class Specialization Example int main() { Foo c; Foo i; c.f(); c.g(); i.f(); i.g(); } /* Output: f using primary template g using primary template f using int specialization g using int specialization */
37
Specializing Selected Member Functions You can do a full specialization of only selected member functions The other member functions come from the primary template (See next slide) Other members can be thus specialized –Statics, Member templates
38
Selective Member Function Spec. Example // Replaces class full specialization: template<> void Foo ::g() { cout << "g using int specialization\n"; } int main() { Foo c; Foo i; c.f(); c.g(); i.f(); i.g(); } /* Output f using primary template g using primary template f using primary template g using int specialization */
39
Partial Specialization of Class Templates Can specialize on a subset of template arguments –leaving the rest unspecified –can also specialize on: “pointer-ness” “const-ness” equality vector is actually a partial specialization –It specializes the data type, but leaves the allocator type “open”: template class vector {…}; The “most specialized, most restricted” match is preferred See next slide
40
template class C { public: void f() { cout << "Primary Template\n"; } }; template class C { public: void f() { cout << "T == int\n"; } }; template class C { public: void f() { cout << "U == double\n"; } }; template class C { public: void f() { cout << "T* used\n"; } }; template class C { public: void f() { cout << "U* used\n"; } };
41
template class C { public: void f() { cout << "T* and U* used\n"; } }; template class C { public: void f() { cout << "T == U\n"; } }; int main() { C ().f(); // 1: Primary template C ().f(); // 2: T == int C ().f(); // 3: U == double C ().f(); // 4: T == U C ().f(); // 5: T* used [T is float] C ().f(); // 6: U* used [U is float] C ().f(); // 7: T* and U* used [float, int] // The following are ambiguous: // 8: C ().f(); // 9: C ().f(); // 10: C ().f(); // 11: C ().f(); // 12: C ().f(); }
42
Class Specialization Summary You must define all functions you want clients to have when specializing a class –An alternative is to only specialize selected member functions, leaving the rest to the primary template You can do full or partial specializations of class templates –Don’t specialize function templates Except as described above for member functions Overloading and specialization of function templates don’t mix well
43
More Points to Ponder About Specialization Template parameters in a specialization don’t have to match the primary template –A total specialization is not a template –A partial specialization is a template But the specialization part (in brackets after the class template name) must match: –template Stack {…}; // 1 type parm –template<> Stack {…}; // 1 type parm –template Stack {…}; // 1 type parm
44
Question What kind of things can you define as members of a class?
45
Answer Variables –Data members; either static or non-static Functions –Member functions, either static and non-static Types –Either nested classes or typedefs Templates –“Member Templates” –This is where need for a special use of the template keyword arises
46
Member Types Nested Classes class bitstring { class bitproxy{ … }; bitproxy operator[](int pos) {…} }; If public, could say: bitstring::bitproxy…
47
Member Types Nested typedefs template class vector { public: class MyIteratorType {…}; typedef MyIteratorType iterator; … }; Can say: vector ::iterator p = v.begin();
48
The typename keyword Can use typename instead of class in a template parameter declaration –Some people use class when the expected argument(s) must not be built-in types –And they use typename when anything goes Sometimes, though, you need to help the compiler know that an identifier represents a type In particular, when you want to use a member type from a template parameter
49
The typename keyword ~ continued ~ Consider the expression T::X –When inside a template with type parameter T The compiler assumes a name qualified by a template type parameter refers to a static member (the parser has to assume something, since T is unknown at first) X is a dependent name If X is a type, use the typename keyword to clue the compiler
50
// A Print function for standard sequences template > class Seq> void printSeq(Seq & seq) { for(typename Seq ::iterator b = seq.begin(); b != seq.end();) cout << *b++ << endl; } int main() { // Process a vector vector v; v.push_back(1); v.push_back(2); printSeq(v); // Process a list list lst; lst.push_back(3); lst.push_back(4); printSeq(lst); }
51
iterator_traits An application of partial specialization A technique for endowing naked pointers with what other iterators have: –difference_type –value_type –pointer –reference –iterator_category
52
Implementing iterator_traits // Primary template template struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; }; // Partial specialization for pointer types template struct iterator_traits { typedef ptrdiff_t difference_type; typedef T value_type; typedef T* pointer; typedef T& reference; typedef random_access_iterator_tag iterator_category; };
53
Using iterator_traits // Print any standard sequence or an array template void print(Iter b, Iter e) { typedef typename iterator_traits ::value_type T; copy(b, e, ostream_iterator (cout, "\n")); }
54
Member Templates Can also nest template definitions inside a class Inside of a class template, too Very handy for conversion constructors: –template class complex { public: template complex(const complex &); –Inside of STL sequences: template deque(InputIterator first, InputIterator last, const Allocator& = Allocator()); Example on next slide
55
A Member Class Template template class Outer { public: template class Inner { public: void f(); }; template template void Outer ::Inner ::f() { cout << "Outer == " << typeid(T).name() << endl; cout << "Inner == " << typeid(R).name() << endl; cout << "Full Inner == " << typeid(*this).name() << endl; } int main() { Outer ::Inner inner; inner.f(); }
56
Output from Previous Example Outer == int Inner == bool Full Inner == Outer ::Inner
57
Member Function Templates Used in practice more than member class templates Cannot be virtual –vtables are fixed-size (traditional compiler techniques) –Since an arbitrary number of instantiations can occur per program, a fixed vtable won’t do –If you want to solve that problem, be my guest :-)
58
March 2008Copyright © 2008, Fresh Sources, Inc. 58 A Generic Programming Session Task: Write a class that simulates the composition of an arbitrary number of functions –h(x) = f(g(x)) –comp(x) f 1 (f 2 (…f n (x)…)) Strategy: –Hold the function pointers in some sequence container –Apply them in reverse order, starting with f n (x)
59
From Specialization to Generalization compose1.cpp (function of double) compose2.cpp (functions of T) compose3.cpp (generalizes sequence type) compose4.cpp (generalizes the callable type) –Using a C++0x feature: function compose5.cpp (deduces T) –Using function::result_type and iterator_traits compose6.cpp (uses std::accumulate) compose7.cpp (deduces iterator type)
60
Name Classification Names can be nicely dichotomized as: –Unqualified x, f( ) –Qualified (provides scope for lookup context) A::x, x.f( ), p->f( ) Names inside templates are also either: –Dependent They depend on a template parameter: e.g., T::iterator We used typename earlier for dependent types –Non-dependent
61
Template Name Lookup Issues Dependent names cannot be resolved when the compiler first encounters a template definition The compiler must wait until instantiation time to resolve those issues –When actual template arguments are known Hence, a 2-step template compilation process: –1) Template Definition –2) Template Instantiation
62
Two-phase Template Compilation Template definition time –The template code is parsed –Obvious syntax errors are caught –Non-dependent names are looked up in the context of the template definition (no waiting needed) Template instantiation time –Dependent names are resolved –Which specialization to use is determined A key motivation for 2-phase lookup Examples follow
63
What Output Should Display? void f(double) { cout << "f(double)" << endl; } template class X { public: void g() { f(1); } }; void f(int) { cout << "f(int)" << endl; } int main() { X ().g(); }
64
Answer f( ) is a non-dependent name, so it is looked up when the template code is parsed f(double) is in scope at the time, so the call resolves to that function, not f(int) Some compilers do the wrong thing here –For historical reasons (the rules were decided on late in the game)
65
A Second Example The underlined calls fail! template class PQV : public vector { Compare comp; public: PQV(Compare cmp = Compare()) : comp(cmp) { make_heap(begin(), end(), comp); } const T& top() const { return front(); } void push(const T& x) { push_back(x); push_heap(begin(), end(), comp); } void pop() { pop_heap(begin(), end(), comp); pop_back(); } };
66
Dependent Base Classes Not considered during name lookup template class PQV : public vector { Compare comp; public: PQV(Compare cmp = Compare()) : comp(cmp) { make_heap(this->begin(),this->end(), comp); } const T& top() const { return this->front(); } void push(const T& x) { this->push_back(x); push_heap(this->begin(),this->end(), comp); } void pop() { pop_heap(this->begin(),this->end(), comp); this->pop_back(); } };
67
Moral “x” is not always the same as “this->x”! In tempaltes, anyway
68
Template Metaprogramming Compile-time Computation! –whoa! Has been proven to be “Turing complete” –means that theoretically, you can do anything at compile time –in practice, it’s used for custom code generation, compile-time assertions, and numerical libraries
69
Runtime Factorial The compiler can do integer arithmetic –Common in legacy preprocessor operations Compile-time recursion: –Use a primary template for the recursion Uses in turn an instantiation of itself –A specialization stops the recursion at the base case See factorial.cpp
70
Runtime binary-to-decimal conversion (from David Abrahams) template struct binary { static const unsigned int value = binary ::value << 1 | N%10; }; // A (full/total) specialization to stop the recursion. template<> struct binary { static const unsigned int value = 0; }; int main() { cout ::value << endl; // 5 cout ::value << endl; // 622 cout ::value << endl; // 231 }
71
Traits A way to store type-specific code for templates –“Compile-time polymorphism” –Use separate template specializations for each type Examples: –std::numeric_limits –std::char_traits
72
std::numeric_limits template class numeric_limits { public: static const bool is_specialized = false; static T min() throw(); // for float, etc. static T max() throw(); static const int digits = 0; static const int digits10 = 0; static const bool is_signed = false; static const bool is_integer = false; static const bool is_exact = false; static const int radix = 0; static T epsilon() throw();... }; float eps = numeric_limit ::epsilon();
73
IEEE Traits template struct IEEE_traits {}; template<> struct IEEE_traits { typedef float FType; enum { nbytes = sizeof(float), nbits = nbytes*8, exp_bits = 8, bias = 127 }; template<> struct IEEE_traits { typedef double FType; enum { nbytes = sizeof(double), nbits = nbytes*8, exp_bits = 11, bias = 1023 };
74
Using IEEE_Traits template bool isinfinity(FType x) { return exponent(x) == IEEE_traits ::bias+1 && fraction(x) == FType(0); } template bool isnan(FType x) { return exponent(x) == IEEE_traits ::bias+1 && fraction(x) != 0; }
75
Policies Similar to Traits –But the emphasis is on functionality (not data) –Template arguments represent implementation strategies Examples: –STL sequences –Allocators –Alexandrescu Singleton
76
C++’s Container Adapters Policies in Action queue, stack, priority_queue Implemented with an underlying sequence data structure –vector, deque, or list or roll your own You glue them together at compile time: –queue > –Default storage “policy” is deque
77
Allocators Recall the definition of std::vector: template > class vector; std::allocator is a memory management policy –It uses new and delete –But you can provide your own custom allocator class A pool allocator, say
78
Alexandrescu’s Singleton Has 4 template parameters: –The class to “singleton-ize” –Storage Policy –Lifetime Policy –Threading Policy Singleton x; –Defaults to SingleThreaded
79
Counting Objects Can use a static data member that tracks the object count Constructors increment Destructors decrement
80
Counting Non-template objects // This is C++ 101: class CountedClass { static int count; public: CountedClass() { ++count; } CountedClass(const CountedClass&) { ++count; } ~CountedClass() { --count; } static int getCount() { return count; } }; int CountedClass::count = 0;
81
How not to share Counting Code class Counted { static int count; public: Counted() { ++count; } Counted(const Counted&) { ++count; } ~Counted() { --count; } static int getCount() { return count; } }; int Counted::count = 0; // All derived classes share the same count! class CountedClass : public Counted {}; class CountedClass2 : public Counted {};
82
The Solution We need a separate count for each class to be counted We need to inherit from a different class for each client class Hence, we need to use both inheritance (OOP) and templates (compile-time polymorphism) See next slide
83
A Template Counter Solution template class Counted { static int count; public: Counted() { ++count; } Counted(const Counted &) { ++count; } ~Counted() { --count; } static int getCount() { return count; } }; template int Counted ::count = 0; // Curious class definitions!!! class CountedClass : public Counted {}; class CountedClass2 : public Counted {};
84
A Curiously Recurring Template Pattern (CRTP) A class, T, inherits from a template that specializes on T! class T : public X {…}; Only valid if the size of X can be determined independently of T –i.e., during Phase 1
85
Singleton via CRTP Inherit Singleton-ness Uses Meyers’ static singleton object approach –Since nothing non-static is inherited, the size is known at template definition time Protected constructor, destructor Disables copy/assign See next slide
86
Singleton via CRTP // Base class – encapsulates singleton-ness template class Singleton { Singleton(const Singleton&); Singleton& operator=(const Singleton&); protected: Singleton() {} virtual ~Singleton() {} public: static T& instance() { static T theInstance; // Meyers' Singleton return theInstance; } };
87
Making a Class a Singleton // A sample class to be made into a Singleton class MyClass : public Singleton { int x; protected: friend class Singleton ; // to create it MyClass() { x = 0; } public: void setValue(int n) { x = n; } int getValue() const { return x; } }; int main() { MyClass& m = MyClass::instance(); cout << m.getValue() << endl; m.setValue(1); cout << m.getValue() << endl; }
88
A Simple Class Template How can we add a stream inserter? –ostream& operator(ostream&, const Box &); template class Box { T t; public: Box(const T& theT) : t(theT) {} };
89
template class Box { T value; public: Box(const T& t) { value = t; } friend ostream& operator &); }; template ostream& operator b) { return os << b.value; } This Won’t Work! Templates Are Different
90
What’s the Problem? The inserter is not a template –But it uses a template argument (T) –This is a problem since it’s not a member function –Our operator<<( ) must be a template What do we want? –We want a distinct specialization for each T –But it can’t be a member template! That would introduce an independent template parameter Aargh!
91
Solution 1 There are special rules for friend function templates to class templates –Use angle brackets in the declaration of the friend Can be empty if the function parameters are sufficient to deduce the template arguments –But… the friend must be previously declared An exception to the usual automatic assumption that the function is in the enclosing scope This also requires a forward declaration of Box See Next Slide
92
Friend Function Templates // Forward declarations template class Box; template ostream& operator<<(ostream&, const Box &); template class Box { T value; public: Box(const T& t) { value = t; } friend ostream& operator (ostream&, const Box &); }; template ostream& operator & b) { return os << b.value; }
93
template class Box { T value; public: Box(const T& t) { value = t; } friend ostream& operator & b) { return os << b.value; } }; Solution 2
94
Making New Friends Define body of operator<< in situ –(i.e.,inside of Box) Such an operator<< is not technically a template! –No angle brackets are used A new ordinary function is created for each specialization of Box! –Names must be suitably mangled –Like inner classes in Java
95
Had Enough? There’s still more to cover! But let’s stop here.
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.