Introduction to C++ Templates Speaker: Bill Chapman, C++ developer, financial company in Mid-Manhattan. Can be reached via: ’[email protected]' This talk will be based entirely on C++ as of 2003 – no C++11 will be used. This will be an introduction to templates. It is assumed the audience Knows C (including pointer arithmetic). Understands C++ classes, methods, constructors, destructors, references, and 'const'. Is familiar with C++ I/O & the C++ 'std::string' class.
42
Embed
Introduction to C++ Templates Speaker: Bill Chapman, C++ developer, financial company in Mid-Manhattan. Can be reached via: [email protected] ' This.
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Introduction to C++ Templates
Speaker: Bill Chapman, C++ developer, financial company in Mid-Manhattan.
This has some disadvantages Not scoped. If anyone, anywhere, declares an identifier
named MAX after that, global or local, they'll get really confusing compile errors.
'Winner' evaluated twice: 'MAX(++i, ++j)', or 'MAX(slowFunction(x), slowFunction(y))'
'double' function
Another approach is to declare an inline typed function:inlinedouble max(double x, double y) { return x > y ? x : y;}
Inefficient if you were just dealing with 'int's.What if you want to take the 'max' of a type that stored unlimited precision ints – can't reliably be converted to and from a double.
The compiler will match 'T' to any type that is passed to the args of this routine, and create a routine for that type.Drawback – what if T is a huge class – say, big tables for which 'operator>' is defined? We're copying the return value, which is expensive. So we do:
'max(4, 7.3)' won't work – passing an int and a float, different types. Template doesn't know which to use, refuses to compile. Templates won't convert types AT ALL when computing types. So suppose we
template <typename A, typename B> inlineconst A& max(const A& a, const B& b) { return a > b ? a : b;}
NOT a good idea – arbitrarily chose 'A', not 'B' as the return type, so 'max(4, 7.3)' will return 7! So we only use the previous implementation.
Making one-type 'max' work
So we go back to our previous implementation, which is basically what's available in the STL in '#include <algorithm>'.
When the function is called, the compiler will generate code for a version of the function with the appropriate 'T'. If two different .cpp files call max<int>, there will be multiple copies of the code for the function generated, all but one of which will be discarded by the linker.
There are ways to tell the compiler not to generate code for a function in the current module, basically promising that another module will do it. We won't get into that. The way people usually do function templates, inline or not, is with the code implementing it visible in the .h file, and let the linker sort out redundancies.
Unix 'qsort' C function
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));Can't handle types with complex assignment, copy c'tor, or d'tor -- must be bitwise copyable. No 'std::string's, for example (std::string MIGHT work, sort of by accident, depending on the details of the implementation of 'qsort' and 'std::string').
Have to cast pointers to 'void *'s, throwing away type checking
Have to provide a special 'compar' function, which must take two 'void *' ptrs, and it cannot be inline.
Bubble Sort Function Template
This function will sort an array, given ptrs to beginning and end.
template <typename TYPE> void mySort(TYPE *begin, TYPE * const end) { for (; begin != end; ++begin) { for (TYPE *rover = begin + 1; rover != end; ++rover) { if (*rover < *begin) { // 'operator<' TYPE tmp(*rover); // copy c'tor *rover = *begin; // assignment *begin = tmp; // assignment } // destructor called on 'tmp' here} } }
Note uses 'operator<', copy c'tor, assignment, and d'tor. All can be inline!!! This could be trivially upgraded to a better sorting algorithm.
Type checking on args, good chance you wanted to implement 'operator<' for TYPE anyway.
STL has 'std::sort' in '#include <algorithm>', along these lines, but much better.
Example: Container Class
Arrays of complex types are hard to do by hand in C++.
Want to be able to efficiently grow & shrink array.
'new[]' must call default c'tor when you allocate array – might prefer to defer c'tor calling until you call copy c'tor when adding elements.
If forget to destroy something, could leak memory.
Hard to grow & shrink an array – lots of copying, have to do just right, call appropriate c'tors & d'tors.
We’ll create a 'container class' template that will do most of this work for you, for any well-behaved type, including complex types. We'll call it 'MyArray'.
Showing the code to implement 'MyArray' will take several slides.
Properties of 'MyArray'
Has two properties: 'capacity' and 'size'. 'size' is how many elements there are in the
container. 'capacity' is how many elements there is room for.
Never calls default c'tor. Only copies copy c'tor when it actually has an element to store into the container.
When container is destroyed, destroys all elements contained.
'capacity' can only grow, never shrinks.
MyArray: Class Declarationtemplate <typename TYPE>class MyArray { // DATA unsigned d_capacity; unsigned d_size; TYPE *d_array; // PRIVATE MANIPULATORS void grow();
// CREATORStemplate <typename TYPE>MyArray<TYPE>::MyArray() : d_capacity(4) , d_size(0) { // use malloc, not 'new' -- don't want c'tors run on // individual elements
In the implementation of 'MyArray', we can check if the parametrized type is a pointer or fundamental type. If it is, we knowWe never have to call destructors on elements.A bitwise copy will work just as well as the copy constructor.
Using this information, we can produce a more optimized implementation for containers of simple types.
Optimizing 'MyArray' With Specializations
The methods where we stand to gain from this optimization are 'grow' and the destructor. First, ’grow’. New code in red:
template <typename TYPE>MyArray<TYPE>::~MyArray() { if (0 == IsPointer<TYPE>::VAL && 0 == IsFundamental<TYPE>::VAL) { TYPE * const end = d_array + d_size; for (TYPE *pt = d_array; pt < end; ++pt) { pt->~TYPE(); } } free(d_array);}
The STL The C++ Standard Template Library
Has functions in '#include <algorithm>- std::max- std::min- std::sort // and many, many others
Has many containers- std::vector // like 'MyArray', but much more powerful- std::set // binary tree, keys only- std::map // binary tree, key + data- std::list // doubly-linked list- std::deque // double-ended queue (pronounced 'deck')- std::hash_set // hash table, keys only, to be replaced by std::unordered_set- std::hash_map // hash table key + data, to be replaced by std::unordered_map // and more
std::vector<std::string> mv(data + 0, data + NUM_DATA); assert(mv.size() == NUM_DATA);
std::sort(&mv[0], &mv[0] + mv.size());
// verify mv is sorted for (unsigned u = 0; u < mv.size() - 1; ++u) { assert(mv[u] <= mv[u + 1]); }
std::cout << "// Produces output:\n"; for (unsigned u = 0; u < mv.size(); ++u) { std::cout << "// " << mv[u] << std::endl; }}
Note the constructor for 'mv’.
Programming With STL Containers
Programmers learn to use the STL Containers as building blocks.
All containers are well-behaved, general-purpose types, with =, ==, <, >, <=, >=, != all defined. Easy to iterate through the elements in any STL container with a loop.
Generally much simpler to build a datastructure out of STL Containers than to build it with naked pointers. And you rarely have to worry about destructing anything or leaking memory. The containers do it all for you.
It is quite common to have a set of vectors, or a map of maps – all combinations are possible.
Calculating Primes At Compile Time
Everything we've covered so far is potentially extremely useful. Calculating primes at compile time is not – it's MUCH easier to calculate them at run-time.
Someone figured out, and mathematically proved, that you can simulate a turning machine with templates at compile time. So in theory you can do anything. In practice, it's an incredibly inefficient (but quite fun) way to do things. Just don't quit your day job.
simplePrime.h
Note that templates can take 'int' and 'bool' for template parameters as well as types. They can't take floats or doubles.
template <int NUM, int DIV>struct IsPrime_Aux { enum { VAL = (NUM % DIV) && IsPrime_Aux<NUM, DIV + 1>::VAL };};template <int NUM>struct IsPrime_Aux<NUM, NUM> { // terminates recursion enum { VAL = 1 };};
template <int NUM>struct IsPrime { enum { BOTTOM = NUM < 2 ? NUM : 2, // BOTTOM = min(NUM, 2); VAL = NUM >= 2 && IsPrime_Aux<NUM, BOTTOM>::VAL };};
The lowest prime number is 2. 0 and 1 are not prime.
'IsPrime<NUM>::VAL' will be 1 if 'NUM' is prime and 0 otherwise. This is the simplest way I could think of to do it.
Calling 'IsPrime'
How do we loop to call 'IsPrime' for a range of numbers? How about:for (int i = 0; i < 100; ++i) { if (IsPrime<i>::VAL) std::cout << i << std::endl;}
Anybody see why there's a problem with this?
'i' is a variable – only types or compile-time constants can be passed in the '<>'s to a template.
We could just line up 100 calls by hand, but we're programmers – such an approach is beneath us. So how do we iterate from 0 to 99 at compile-time?
simplePrimes.cpp#include "simplePrime.h"
template <int FROM, int TO>struct ShowPrimes { static void run() { ShowPrimes<FROM, TO – 1>::run(); if (IsPrime<TO>::VAL) std::cout << TO << std::endl;} };template <int END>struct ShowPrimes<END, END> { static void run() { if (IsPrime<END>::VAL) std::cout << END << std::endl;} };
int main() { enum { START = 0 }; // max value of START is 923 ShowPrimes<START, START + 100>::run();}
enum { VAL = 2 == NUM || (NUM > 1 && NUM % 2 && Aux<3>::VAL) };};
This implementation of 'IsPrime<NUM>' is much more efficient than the last one. It special cases the check for even numbers, and other than that only tries to divide odd numbers, up past the square root of 'NUM', at which point it stops, where 'simplePrime.h' tried dividing all numbers, odd and even, from 2 up to 'NUM – 1'.
The means of stopping recursion is interesting. We use a specialized template with the 2nd boolean argument 'DONE' specialized to detect when the divisor has risen past the square root of 'NUM', then recursion terminates.
betterPrime.cpp#include "betterPrime.h”template <int FROM, int TO>struct ShowPrimes { static void run() { ShowPrimes<FROM, TO – 1>::run(); if (IsPrime<TO>::VAL) std::cout << TO << std::endl;} };template <int END>struct ShowPrimes<END, END> { static void run() { if (IsPrime<END>::VAL) std::cout << END << std::endl;} };int main() { enum { START = 0 }; // max value of START is 3403924 ShowPrimes<START, START + 100>::run();}$ ./a.out23571113. . .
Just like 'simplePrimes.cpp', except includes 'betterPrime.h' instead of 'simplePrime.h' and produces the same output 0 - 100
Generating Primes in Error Messages
It would be fun to generate primes without creating an executable – that is, nothing gets to be done at run time. In struct 'AssertPrime' we force a compiler error by dividing by 0 if 'NUM' is prime.#include "betterPrime.h"
template <int FROM, int TO>struct ShowPrimes { enum { A = ShowPrimes<FROM, TO – 1>::VAL, VAL = AssertPrime<TO>::VAL + A };};template <int END>struct ShowPrimes<END, END> { enum { VAL = AssertPrime<END>::VAL };};
enum { VAL = ShowPrimes<1, 100>::VAL };
Notice that <iostream> is never included and there is no 'main'.
Error Messages from assertPrimes.cpp
Gives rise to a horrid torrent of error messages. But notice 'AssertPrime<N>': assertPrimes.cpp:13:1: error: expected unqualified-id before ‘}’ tokenassertPrimes.cpp: In instantiation of ‘AssertPrime<2>’:assertPrimes.cpp:10:10: recursively instantiated from ‘ShowPrimes<1, 99>’assertPrimes.cpp:10:10: instantiated from ‘ShowPrimes<1, 100>’assertPrimes.cpp:19:32: instantiated from hereassertPrimes.cpp:5:10: warning: division by zero [-Wdiv-by-zero]assertPrimes.cpp:5:10: error: ‘(1 / 0)’ is not a constant expressionassertPrimes.cpp:5:10: error: enumerator value for ‘VAL’ is not an integer constantassertPrimes.cpp: In instantiation of ‘AssertPrime<3>’:assertPrimes.cpp:10:10: recursively instantiated from ‘ShowPrimes<1, 99>’assertPrimes.cpp:10:10: instantiated from ‘ShowPrimes<1, 100>’assertPrimes.cpp:19:32: instantiated from hereassertPrimes.cpp:5:10: warning: division by zero [-Wdiv-by-zero]assertPrimes.cpp:5:10: error: ‘(1 / 0)’ is not a constant expressionassertPrimes.cpp:5:10: error: enumerator value for ‘VAL’ is not an integer constantassertPrimes.cpp: In instantiation of ‘AssertPrime<5>’:. . .
Grepping Error Output for 'AssertPrime'
$ g++ assertPrimes.cpp 2>&1 | grep ’AssertPrime<’assertPrimes.cpp: In instantiation of ‘AssertPrime<2>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<3>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<5>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<7>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<11>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<13>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<17>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<19>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<23>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<29>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<31>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<37>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<41>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<43>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<47>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<53>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<59>’:assertPrimes.cpp: In instantiation of ‘AssertPrime<61>’:. . .