COMP 345 Advanced Program Design with C 1

  • Slides: 24
Download presentation
COMP 345 - Advanced Program Design with C++ 1 Click to edit Master title

COMP 345 - Advanced Program Design with C++ 1 Click to edit Master title style ADVANCED PROGRAM DESIGN WITH C++ Templates metaprogramming Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 2 Metaprogramming • Metaprogramming is about

COMP 345 - Advanced Program Design with C++ 2 Metaprogramming • Metaprogramming is about writing programs that can take programs as data to • • be computed. A metaprogram is a program that can read, write, analyze, generate or change programs. • metalanguage : language in which a metaprogram is written. • object language : language used to express manipulated programs. If the metalanguage is also the object language, this allows to write programs that can change themselves, which is also known a reflective programming. Metaprogramming has been long part of programming languages, starting from Lisp in the early 1960 s, that allowed a program to call its own interpreter to execute a string as a program (through the eval() function), and thus allowing a program to generate an execute programs and thus invent/change its own execution. Reflective programming took a different flavor in the 1970 s with the development of systems like Smalltalk, whose compiler and runtime system was written in Smalltalk itself, which allowed its runtime system to manipulate Smalltalk programs as data objects. Nowadays, many “dynamic languages” allow some form of reflective programming. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 3 Template metaprogramming • Template metaprogramming

COMP 345 - Advanced Program Design with C++ 3 Template metaprogramming • Template metaprogramming is a particular kind of metaprogramming technique in which templates written in a metalanguage are used by a pre-compiler to generate intermediate code in a specific programming language, which is then compiled by the main compiler along with the regular source code. • The template is first declared using a meta language. • When it is referred to in some code in a particular context (either in regular source code or in another template), the template is instantiated to this particular context of use through object code generation. • The template instance is then a normal object code component that can be compiled/linked with other software components written in the source language. • Any time the same template is used somewhere else with the same context, the previously generated object code instance is used. • In template metaprogramming, from the perspective of the metaprogram, the generated source code is generated in the object language. • In many implementations, such as C++, the metalanguage is a hybrid language that allows the use of the object language in the metaprograms, as well as to use metaprogram instances in the object language. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 4 C++ templates • C++ provides

COMP 345 - Advanced Program Design with C++ 4 C++ templates • C++ provides this feature, which enables the programmer to write and use C++ • • templates written in a meta language that is a slight variation of C++, and that can use C++ in their definitions. C++ code with templates is thus written in a hybrid language. C++ templates are used to write type-abstract code that expresses a generic solution independently of some type of values used or manipulated by the implementation. The most common kinds of templates are containers, such as those implemented in the Standard Templates Library (STL). For example, a stack is a container that has some behavior (top, push, pop) which is common across and independent of the type of values stored in the stack. Writing a C++ template allows to write the logic of the code while making abstraction of some of the types involved in order to make this code applicable across different types. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 5 C++ templates: compilation process •

COMP 345 - Advanced Program Design with C++ 5 C++ templates: compilation process • When C++ code uses some templates, the compilation process is preceded by template metaprocessing: • Reads the templates. • Finds all uses of the template. • Generates a concrete version of the template (i. e. template instance) for each different context of use i. e. type parameter(s) used. • Links uses of the template with the correct concrete version. • Normal compilation is applied to the generated/altered code. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 6 C++ templates • One might

COMP 345 - Advanced Program Design with C++ 6 C++ templates • One might want to define a function max to return the maximum value between its two parameters, to be used as such: void main() { cout << "max(10, 15) = " << max(10, 15) << endl; cout << "max('k', 's') = " << max('k', 's') << endl; cout << "max(10. 1, 15. 2) = " << max(10. 1, 15. 2) << endl; } • In order to make this work, three different functions are needed: int max(int a, int b) { return a > b ? a : b; } char max(char a, char b) { return a > b ? a : b; } float max(float a, float b) { return a > b ? a : b; } • For each type that we want max() to be applicable to, we need to explicitly overload the max() function. In many cases (such as this one), the implementation code remains the same and the only difference is the type of some value(s) used in the processing. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 7 C++ templates • A function

COMP 345 - Advanced Program Design with C++ 7 C++ templates • A function template can be defined to achieve the same result: // template free function definition template <typename T> T max(T a, T b) { return a > b ? a : b; } // generated template instances // uses of the function template triggers // template instances generation at compile-time void main() { cout << "max(10, 15) = " << max(10, 15) << endl; cout << "max('k', 's') = " << max('k', 's') << endl; cout << "max(10. 1, 15. 2) = " << max(10. 1, 15. 2) << endl; } char max(char a, char b) { return a > b ? a : b; } int max(int a, int b) { return a > b ? a : b; } float max(float a, float b) { return a > b ? a : b; } • The template function declaration/definition is first registered by the compiler. • Then, upon every use of the template function, the metaprocessor instantiates a template instance depending on the context of use of the template and organizes proper linkage to each instantiated function. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 8 C++ template: template class and

COMP 345 - Advanced Program Design with C++ 8 C++ template: template class and template method example // template class declaration template <typename T> class Stack { public: Stack(int = 10); ~Stack() { delete[] stack. Ptr; } int push(const T&); int pop(T&); int is. Empty()const { return top == -1; } int is. Full() const { return top == size - 1; } private: // use of template class int size; // triggers template instantiation int top; void main() { T* stack. Ptr; Stack<float> fs(5); }; float f = 1. 1; while (fs. push(f)) { cout << f << ' '; f += 1. 1; } while (fs. pop(f)) cout << f << ' '; Stack<int> is; int i = 1; while (is. push(i)) { cout << i << ' '; i += 1; } while (is. pop(i)) cout << i << ' '; //template class method definitions template <typename T> Stack<T>: : Stack(int s) { size = s > 0 && s < 1000 ? s : 10; top = -1; // initialize stack. Ptr = new T[size]; } // push an element onto the Stack template <typename T> int Stack<T>: : push(const T& item) { if (!is. Full()) { stack. Ptr[++top] = item; return 1; // push successful } return 0; // push unsuccessful } // pop an element off the Stack template <typename T> int Stack<T>: : pop(T& pop. Value) { if (!is. Empty()) { pop. Value = stack. Ptr[top--]; return 1; // pop successful } return 0; // pop unsuccessful } } Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 9 C++ templates: template operator •

COMP 345 - Advanced Program Design with C++ 9 C++ templates: template operator • Operators can also be defined as templates: template <typename T> ostream& operator<<( ostream& output, Stack<T> stack) { int i; while (stack. pop(i)) output << i << ' '; return output; } • Template operators can also be declared as fiends of classes, whether they are template classes or not. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 10 Example: smart pointers • Smart

COMP 345 - Advanced Program Design with C++ 10 Example: smart pointers • Smart pointers such as unique_ptr, shared_ptr, weak_ptr, auto_ptr, are all implemented as templates whose goal is to provide automated management of the allocation/deallocation associated with pointers. • They define a template class that holds a pointer as a data member and implements all the required mechanism to create/destroy, copy, and assign the pointed object in a systematic manner, avoiding frequent memory allocation/deallocation problems. • On the right is a template class implementing a simple smart pointer implementing deep copies and deletion. Concordia University template<class T> class my_auto_ptr{ T* m_ptr; public: // default constructor my_auto_ptr(T* ptr = nullptr); : m_ptr(ptr) { } // deep copy constructor my_auto_ptr(my_auto_ptr &ptr){ m_ptr = new T; *m_ptr = *ptr. m_ptr; } // deep copy assignment operator my_auto_ptr& operator=(const my_auto_ptr& ptr){ if (this == &ptr) return *this; delete m_ptr; m_ptr = new T; *m_ptr = *ptr. m_ptr; return *this; } // destructor ~my_auto_ptr(){ delete m_ptr; } // dereference operators T& operator*() const { return *m_ptr; } T* operator->() const { return m_ptr; } }; Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 11 Example: smart pointers • This

COMP 345 - Advanced Program Design with C++ 11 Example: smart pointers • This other template class uses the notion of “move semantics” to implement smart pointers that move the pointed resource rather than copy it. • This uses the notion of rvalue reference (&&), as opposed to regular references, which are lvalue references (&). • This leads to less objects being created. • Both approaches can be combined - see the full example in the lab material. Concordia University template<class T> class my_move_auto_ptr{ T* m_ptr; public: // default constructor my_move_auto_ptr(T* ptr = nullptr) : m_ptr(ptr){ } // destructor ~my_move_auto_ptr(){ delete m_ptr; } // disable copy constructor my_auto_ptr(const my_auto_ptr& ptr) = delete; // Move constructor my_move_auto_ptr(my_move_auto_ptr&& a) { m_ptr = a. m_ptr; a. m_ptr = nullptr; } // disable regular assignment operator my_auto_ptr& operator=(const my_auto_ptr& ptr) = delete; // move semantics assignment operator my_move_auto_ptr& operator=(my_move_auto_ptr&& a) { if (&a == this) return *this; delete m_ptr; m_ptr = a. m_ptr; a. m_ptr = nullptr; return *this; } // dereference operators T& operator*() const { return *m_ptr; } T* operator->() const { return m_ptr; } }; Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 12 Explicit template specialization • A

COMP 345 - Advanced Program Design with C++ 12 Explicit template specialization • A great limitation of templates is that their applicability across different types is bound by the validity of the code that they use for all types that they will eventually be used for. • Take our previous example max function template, which we might later want to use for strings: // template free function definition template <typename T> T max(T a, T b) { return a > b ? a : b; } void main() { cout << "max(10, 15) = " << max(10, 15) << endl; cout << "max('k', 's') = " << max('k', 's') << endl; cout << "max(10. 1, 15. 2) = " << max(10. 1, 15. 2) << endl; cout << "max("Foo", "Bar") = " << max("Foo", "Bar") << endl; } • Here the problem is that applying the > operator on a char* (type of "Foo" and "Bar") compares the pointer values, not the strings that they point to, leading to incorrect result. • We need to redefine the code for the implementation of the max template function to use a comparison operation that also applies to char*. However, such an operation might not exist, leading us to write type-dependent code. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 13 Explicit template specialization • Alternately,

COMP 345 - Advanced Program Design with C++ 13 Explicit template specialization • Alternately, we can also use the notion of explicit template specialisation to provide an alternate implementation for a specific instance of the template: template <> char* max(char* a, char* b) { return strcmp(a, b) > 0 ? a : b; } • This defines a specific instance of the function template max that is applicable when both parameters are of type char*. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 14 Explicit template specialization • The

COMP 345 - Advanced Program Design with C++ 14 Explicit template specialization • The same concept can be applied to template classes • This defines a specific instance of the template class Formatter that is applicable when the parameters is of type char*. // Template class declaration and definition template <typename T> class Formatter { T* m_t; public: Formatter(T* t) : m_t(t) { } void print() { cout << *m_t << endl; } int main() }; { int i = 157; // Use the generic template with int as the argument. // Specialization of template class Formatter<int> formatter 1(&i); // for type char* template<> class Formatter<char*> char str[10] = "string 1"; { char* str 1 = str; char** m_t; // Use the specialized template. public: Formatter<char*> formatter 2(&str 1); Formatter(char** t) : m_t(t) { } void print() { formatter 1. print(); // 157 cout << "Char: " << **m_t << endl; formatter 2. print(); // Char: s } } }; Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 15 Pitfall: C++ templates and static

COMP 345 - Advanced Program Design with C++ 15 Pitfall: C++ templates and static data members and local variables • As they are translated to separate classes, each template class instance has its own copies of any static variables or members. template <class T> class X { public: static T s ; }; int main() { X<int> xi ; X<char*> xc ; } int main() { X<int> xi ; cout << "xi. s = " << xi. s << endl ; X<char*> xc ; cout << "xc. s = " << xc. s << endl ; return 0 ; } • Here, X<int> has its own static member int s, which is shared by all instances of the type X<int>. • X<char*> has its own static member char* s, which is shared by all instances of the type X<char*>. • Static data members are initialized outside of the class declaration (as are static members of regular classes) using the following syntax: template <> int X<int>: : s = 3 ; template <> char* X<char*>: : s = "Hello" ; • Each template function instance has its own copy of any static local variables declared in a function template: Concordia University template <class T> void f(T t) { static T s = 0; s = t ; cout << "s = " << s << endl ; } Department of Computer Science and Software Engineering int main() { f(10) ; f("Hello") ; return 0 ; } Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 16 Pitfall: Templates and separate compilation

COMP 345 - Advanced Program Design with C++ 16 Pitfall: Templates and separate compilation • Templates are not classes or functions, they are a declaration that is used to generate classes when an instantiation is required. • This poses the additional requirement that all template declarations should be available to any compilation unit that is using it. • Let us take the Stack template example. According to standard class definition practice, we have: • Stack. h that contains the declaration of the Stack template. • Stack. cpp that contains the declarations of the member functions of the Stack class, which are themselves function templates. • Stack. Driver. cpp that declares two variable of type Stack<float> and Stack<int>. Each of these declarations trigger the compile-time generation of two specific Stack classes. As we have, according to standard practice, a #include “Stack. h” in Stack. Driver. cpp, the declaration of the Stack template is available at compile time. • However, Stack. Driver. cpp calls the functions push() and pop() on the stack objects, which triggers the compile-time generation of two specific Stack<T>. push() and Stack<T>. pop(). As the declaration of these templates are in Stack. cpp (a different compilation unit), linkage fails. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 17 Pitfall: Templates and separate compilation

COMP 345 - Advanced Program Design with C++ 17 Pitfall: Templates and separate compilation // Stack. h // template class declaration template <typename T> class Stack { public: Stack(int = 10); ~Stack() { delete[] stack. Ptr; } int push(const T&); int pop(T&); int is. Empty()const { return top == -1; } int is. Full() const { return top == size - 1; } private: // Stack. Driver. cpp int size; #include "Stack. h" int top; void main() { T* stack. Ptr; Stack<float> fs(5); }; float f = 1. 1; while (fs. push(f)) { cout << f << ' '; f += 1. 1; } while (fs. pop(f)) cout << f << ' '; Stack<int> is; int i = 1. 1; while (is. push(i)) { cout << i << ' '; i += 1; } while (is. pop(i)) cout << i << ' '; } Concordia University Stack. cpp //template class method definitions #include "Stack. h" template <typename T> Stack<T>: : Stack(int s) { size = s > 0 && s < 1000 ? s : 10; top = -1; // initialize stack. Ptr = new T[size]; } // push an element onto the Stack template <typename T> int Stack<T>: : push(const T& item) { if (!is. Full()) { stack. Ptr[++top] = item; return 1; // push successful } return 0; // push unsuccessful } // pop an element off the Stack template <typename T> int Stack<T>: : pop(T& pop. Value) { if (!is. Empty()) { pop. Value = stack. Ptr[top--]; return 1; // pop successful } return 0; // pop unsuccessful } Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 18 Pitfall: Templates and separate compilation

COMP 345 - Advanced Program Design with C++ 18 Pitfall: Templates and separate compilation • This means that each compilation unit that is making use of a template must have the template declarations available internally. • If a template class is declared separately from its template methods, both need to be united in order to be used across different compilation units. • There are different ways to achieve that: • Declare all methods as inline, forcing everything into the header file. • Put all methods’ definitions in the header file with the template class declaration. • Other related solutions: • Put some explicit template instantiation declarations at the end of the Stack. cpp file, e. g. template class Stack<int>; template class Stack<float>; • This will force the generation of these specific classes. However, only those will be available for linkage. • Put #include “Stack. cpp” at the end of Stack. h, forcing the implementation of Stack. cpp into compilation units that do a #include “Stack. h”. This is against the separate compilation unit principle. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 19 C++ templates vs. Java generics

COMP 345 - Advanced Program Design with C++ 19 C++ templates vs. Java generics • While Java generics syntactically look like C++ templates and are used to achieve the same purpose, it is important to note that they are not implemented using the same concepts, nor do they provide the same programming features. • Java generics simply provide compile-time type safety and eliminate the need for explicit casts when using type-abstract types and algorithms. • Java generics use a technique known as type erasure, and the compiler keeps track of the generic definitions internally, hence using the same class definition at compile/run time. • A C++ template on the other hand use template metaprogramming, by which whenever a template is instantiated with a new type parameter, the entire code for the template is generated adapted to the type parameter and then compiled, hence having several definitions for each template instance at run time. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 20 C++ templates vs. Java generics

COMP 345 - Advanced Program Design with C++ 20 C++ templates vs. Java generics • In Java, a class or method that is defined with a parameter for a type is called a generic class/method or a parameterized class/method: /** * Generic class that defines a wrapper class around a single * element of a generic type. */ public class Box<T extends Number> { private T t; public void set(T t) { this. t = t; } • For classes, the type parameter is • • • included in angular brackets after the class name in the class definition heading. For methods, the type parameter is included before the method definition. Methods can define/use additional type parameters additional to their class’ type parameters. The type parameters are to be used like other types used in the definition of a class/method. When a generic class is used, the specific type to be plugged in is provided in angular brackets. When a generic method is called, its call’s parameter/return type are plugged in. public T get() { return t; } public void inspect(){ System. out. println("T: " + t. get. Class(). get. Name()); } /** * Generic method that uses both the generic type of the class * it belongs to, as well as an additional generic type that is * bound to the Number type. */ public <U> void inspect. With. Additional. Type(U u){ System. out. println("T: " + t. get. Class(). get. Name()); System. out. println("U: " + u. get. Class(). get. Name()); } public static void main(String[] args) { Box<Integer> integer. Box = new Box<Integer>(); integer. Box. set(new Integer(10)); integer. Box. inspect(); integer. Box. inspect. With. Additional. Type("Hello world"); Integer i = integer. Box. get(); } } Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 21 C++ templates vs. Java generics

COMP 345 - Advanced Program Design with C++ 21 C++ templates vs. Java generics • In Java, when the class is compiled, type erasure is applied on the type parameter : /** * Generic class that defines a wrapper class around a single * element of a generic type. */ public class Box { private Number t; • Every occurrence of the type parameter is replaced with the highest type applicable to the type parameter. • If a type bound was specified, this type is applied. If no type bound was specified, Object is used. • After this has been applied, the class is a raw type. • for each specific use of the generic class or method : • Every use of the generic class is converted into the use of its corresponding raw type. • If a value is extracted from a generic class or returned from a generic method that is of the type parameter type, its type need to be casted to the type used at instantiation. Concordia University public void set(Number t) { this. t = t; } public Number get() { return t; } public void inspect(){ System. out. println("T: " + t. get. Class(). get. Name()); } /** * Generic method that uses both the generic type of the class * it belongs to, as well as an additional generic type that is * bound to the Number type. */ public void inspect. With. Additional. Type(Object u){ System. out. println("T: " + t. get. Class(). get. Name()); System. out. println("U: " + u. get. Class(). get. Name()); } public static void main(String[] args) { Box integer. Box = new Box(); integer. Box. set(new Integer(10)); integer. Box. inspect(); integer. Box. inspect. With. Additional. Type("Hello world"); Integer i = (Integer)integer. Box. get(); } } Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 22 C++ templates: facts • Using

COMP 345 - Advanced Program Design with C++ 22 C++ templates: facts • Using C++ templates makes compilation time slower, especially if a numerous • • different template instantiations are required, or if template declarations are using other template declarations, or are declared recursively. May result in unnecessary code bloat if explicit template instantiation is overused. Due to the way templates are processed, using templates forces to reconsider the practice of separating class/function declarations in header files and their implementation in implementation files. As the template instances are generated code that is later being compiled, error reporting tends to be cryptic and seemingly unrelated to the source code. C++ templates are fundamentally a different concept than Java/C# generic classes. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 23 References • Bjarne Stroustrup. The

COMP 345 - Advanced Program Design with C++ 23 References • Bjarne Stroustrup. The C++ Programming • • Language. Chapter 23. Fourth Edition, Addison Wesley. Daniel Liang. Introduction to Programming with C++. Chapter 12. Third Edition, Pearson Education. http: //users. cis. fiu. edu/~weiss/Deltoid/vcstl/templates http: //stackoverflow. com/questions/495021/why-can-templates-only-beimplemented-in-the-header-file M. D. Mc. Ilroy. Mass-Produced Software Components, Proceedings of the 1 st International Conference on Software Engineering, Garmisch Partenkirchen, Germany, 1968. Barbara Liskov, Alan Snyder, Russell Atkinson, and Craig Schaffert. Abstraction mechanisms in CLU. Commun. ACM 20, 8 (August 1977), 564 -576. doi=10. 1145/359763. 359789 Joseph A. Goguen. Parameterized Programming. IEEE Trans. Software Eng. 10(5) 1984. David R. Musser, Alexander A. Stepanov. Generic Programming. In International Symposium on Symbolic and Algebraic Computation (ISSAC 1988). Lecture Notes in Computer Science 358, Springer-Verlag, 1989, pp 13 -25. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020

COMP 345 - Advanced Program Design with C++ 24 References • learncpp. com. Intro

COMP 345 - Advanced Program Design with C++ 24 References • learncpp. com. Intro to smart pointers and move semantics. • learncpp. com. Move constructors and move assignment. Concordia University Department of Computer Science and Software Engineering Joey Paquet, 2007 -2020