// -*- mode: c++ -*- // (set-default-font (format "-xos4-terminus-bold-*-*-*-24-*-*-*-*-*-*-*")) /* C++ Templates ============= 2007.11.08. http://ortros/~salvi/templates/ Overview -------- 1) A problem with "traditional" programming 2) What is a template? 3) Template functions 4) Template classes 5) Specialization 6) Placement of templates 7) Hands-on exercises 8) The modified "command" example 9) Homework 10) Bibliography */ ------------------------------------------------------------------------------- // 1. A problem with "traditional" programming // First look at this function: inline int const &max(int const &a, int const &b) { if(a < b) return b; return a; } int main(int argc, char *argv[]) { int x = 2; std::cout << max(x, 3) << std::endl; // prints 3 double y = 4.5; std::cout << max(y, 3) << std::endl; // prints 4 (4.5 is converted to 4) // ... } // Let's create a max function for doubles! // We just need to replace every "int" with "double"... ------------------------------------------------------------------------------- // So what we get is inline double const &max(double const &a, double const &b) { if(a < b) return b; return a; } // Which works, but it has the same code all over again. // Code repetition is bad, because: // - if there is a bug in it, it will be copied to the new functions // - if you want to modify it, you have to modify all of them // - the source code becomes much longer than it should // - easy to make new bugs ------------------------------------------------------------------------------- // Let's look at another example. class IntMatrix { public: IntMatrix(int rows, int cols) : _rows(rows), _cols(cols) { } void IntMatrix operator+(IntMatrix const &m); void IntMatrix operator-(IntMatrix const &m); void IntMatrix operator*(IntMatrix const &m); // ... private: int _rows, _cols; int **_array; // integer values }; // This class can be very large, with a lot of methods, operators and friends. // If we want to create another class that handles doubles or booleans etc., // we have to copy all of this and change every IntMatrix to BoolMatrix and // some of the "int"s to "bool"s. // We don't want this. ------------------------------------------------------------------------------- // 2. What is a template? /* A template is a function or class that misses some parameters: - the type of some variables - the value of a constant + How does it work? The compiler generates the code for every use. Only those classes, functions and member functions are generated that are called somewhere in the code. + Do we have to specify those missing parameters? Sometimes yes and sometimes no. They can be left out when the compiler can deduce them. + So, how do templates look like?! Let's see some function templates first. */ ------------------------------------------------------------------------------- // 3. Template functions template<typename T> // tells the compiler that T stands for the missing type inline T const &max(T const& a, T const& b) { if(a < b) return b; return a; } // How can we use it? int a = 3, b = 5; double c = 5.5 std::string s1 = "cat", s2 = "dog"; std::cout << max(a, b) << std::endl; // prints 5 std::cout << max(s1, s2) << std::endl; // prints dog std::cout << max(a, s1) << std::endl; // doesn't compile std::cout << max(b, c) << std::endl; // doesn't compile std::cout << max<double>(b, c) << std::endl; // prints 5.5 std::cout << max(static_cast<double>(b), c) << std::endl; // prints 5.5 // "static_cast<double>(b)" is the safe way to say "(double)b" // (in C++ there are four type of casts: // const_cast dynamic_cast reinterpret_cast static_cast) ------------------------------------------------------------------------------- // Excursion - Casting // Sometimes we want to pass variables to functions that have similar types, // but not the same. In plain C, we could use double pi = 3.14; int a = (int)pi; // ... and we can do this in C++, too, but it can lead to many errors. // It is better to use the four casting keywords of C++: void someFunction(int &p) { ... } // there is no const, but it leaves p alone int const a = 10; someFunction(a); // error someFunction(const_cast<int>(a)); // compiles double pi = 3.14; int a = static_cast<int>(pi); // casts to integer without runtime check ParentClass *a = subclass_object; void someFunction(SubClass *s) { ... } someFunction(a); // error someFunction(dynamic_cast<SubClass>(a)); // compiles and does a runtime check reinterpret_cast<type>(object); // is for pointers of incompatible type, // rarely used in good programming style ------------------------------------------------------------------------------- // Back to templates // A template may have more than one parameter: template<typename RT, typename T1, typename T2> inline RT const &max(T1 const& a, T2 const& b) { if(a < b) return b; return a; } // we can use it like max<double, int, double>(1, 2.3); // take an int and a double, gives a double // or max<double>(1, 2.3); // since T1 and T2 can be deduced // The template parameter can also be a value: template<typename T, int VALUE> T addValue(T const &x) { return x + VALUE; } ------------------------------------------------------------------------------- // 4. Template classes // Template classes are created the same way as template functions: template<typename T = double> // class templates may have default values class Matrix { Matrix(int rows, int cols); void operator+(Matrix<T> const &m); // ... private: int _rows, _cols; T **_array; }; // The methods can be defined like template<typename T> Matrix<T>::operator+(Matrix<T> const &m) { // ... } // ... and when we create an object: Matrix<int> m(3, 5); // a 3 by 5 matrix of integers Matrix<double> n(10, 10); // a 10 by 10 matrix of doubles ------------------------------------------------------------------------------- // 5. Specialization // Templates can be specialized for certain values. This allows us to handle // special cases in an efficient way. // For example, the Matrix class may have a member for constant multiplication: template<typename T> class Matrix { // ... void operator*(double x); // multiply every element with x // ... }; // ... but what should this do, when T is not double? // For example when T is std::string? It should give an error. // We can specialize operator* for the case when T = double: template<> Matrix<double>::operator*(double x) { // ... } // The specialized template always has higher priority. ------------------------------------------------------------------------------- // 6. Placement of templates // Template functions should be placed in the _header files_ . // We don't want the header file to get too big, so we will use two files. // In the main header file, we will only insert declarations: SomeClass.hh: ------------ #ifndef SOME_CLASS_HH #define SOME_CLASS_HH template<typename T> class Matrix { public: Matrix(int rows, int cols); void operator+(Matrix<T> const &m); // ... private: // ... }; #include "SomeClass.tcc" // put the definitions in this file #endif // SOME_CLASS_HH ------------------------------------------------------------------------------- // Then we put the declarations in another file: SomeClass.tcc: // under Visual C++, maybe you should use .hpp -------------- template<typename T> Matrix<T>::Matrix() { // ... } template<typename T> Matrix<T>::operator+(Matrix<T> const &m) { // ... } // ... /* This is everything you need to know about templates for now. Of course, there is much more than this... ... but this will be enough for most applications. */ ------------------------------------------------------------------------------- // 7. Hands-on exercises /* Do these excercises now (you can ask me if something is not clear) A testing C++ file is available at: http://ortros/~salvi/templates/exercises.html 1) Write a template function abs that gives the absolute value of a variable of any type. It should be used like: abs(-3); => 3 abs<int>(-4.1); => 4 (this should generate a compiler warning) 2) Write a class Stack that is a list of elements, empty at first. It has the following methods: - isEmpty() : returns true if the list is empty, false otherwise - push(e) : puts e at the start of the list - pop() : removes the first element from the list and returns it 3) Write a function makeArray that gives an array of n elements of any type, and initializes it with a given element. It should be used like: makeArray<3, int>(10); => [10, 10, 10] (array of int) makeArray<2>(std::string("templates")); => ["templates" "templates"] */ ------------------------------------------------------------------------------- // 8. The modified "command" example /* The command example we saw last week has a new class Vector. But Vector uses only doubles and Point only ints. This should be done with templates, then the two types will be in harmony. We create template types, which are used like Point<int> or Vector<double>. You can download the modified version from http://ortros/~salvi/templates/command.zip Compare the new code with the original, there are a lot of changes, mainly - in Point.h and Vector.h - in Point.tcc and Vector.tcc (which were originally Point.cpp and Vector.cpp) */ ------------------------------------------------------------------------------- // 9. Homework // Finish the exercises, if you haven't done it already. // Change the Point and Vector classes in the "command" program, to have // the dimension as another parameter (the default is 3), so we can say: Point<int, 2> point; // two-dimensional point with integer coordinates // or Vector<double> vector; // three-dimensional vector with double coordinates /* That's all for today! */ ------------------------------------------------------------------------------- // 10. Bibliography /* The following books contain very good learning materials: - D. Vandevoorde, N. M. Josuttis: C++ Templates: The Complete Guide, Addison-Wesley, 2002. - R. Lischner: C++ in a Nutshell, O'Reilly, 2003. - D. Abrahams, A. Gurtovoy: C++ Template Metaprogramming, Addison-Wesley, 2004. */