diff --git a/notebooks/lecture3final.ipynb b/notebooks/lecture3final.ipynb new file mode 100644 index 0000000..94f732a --- /dev/null +++ b/notebooks/lecture3final.ipynb @@ -0,0 +1 @@ +{"metadata":{"celltoolbar":"Slideshow","kernelspec":{"display_name":"C++17","language":"C++17","name":"xcpp17"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"code","source":"//Please run this cell before the lecture --JL\n\n#include \n#include ","metadata":{"tags":[],"slideshow":{"slide_type":"skip"},"trusted":true},"execution_count":5,"outputs":[],"id":"092fcb22"},{"cell_type":"markdown","source":"# Object-oriented scientific programming with C++\n\nMatthias Möller, Jonas Thies, Cálin Georgescu, Jingya Li (Numerical Analysis, DIAM)\n\n\n\nLecture 3","metadata":{"slideshow":{"slide_type":"slide"}},"id":"d640841f"},{"cell_type":"markdown","source":"##
Overview
\nDuring last several lectures, we learned polymorphism, that is, inheritance of one class from another class\n- Implementation of common functionality in **base class** (possibly realised as **abstract class** that cannot even be instantiated)\n- Derivation of specialised class(es) from the base class that\n - implement the missing functionality (**pure virtual functions**)\n - override generic functionality by specialised variants (**virtual functions**)\n - reuse all other functionality from the base class","metadata":{"slideshow":{"slide_type":"slide"}},"id":"c9ffa03c"},{"cell_type":"markdown","source":"##
Overview
\nToday, a more careful view on polymorphism\n- **Static polymorphism**: static binding/method overloading\n- **Dynamic polymorphism**: dynamic binding/method overriding\nAuto functionality of C++11/14/17.\n\n**Template meta programming**\n- A powerful variant of static polymorphism","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"9b0cdb3b"},{"cell_type":"markdown","source":"##
Quiz: Guess what happens
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"8f8bf584"},{"cell_type":"code","source":"struct Base {\n virtual void hello() const = 0;\n virtual ~Base() {} // virtual destructor for proper cleanup\n};\n\nstruct Derived : public Base {\n void hello() const override // Correct function name to override the base class method\n {\n std::cout << \"Hallo\\n\";\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":6,"outputs":[],"id":"e18b2239"},{"cell_type":"markdown","source":"##
Quiz: Guess what happens
\nRun the main function to verify your guess","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"74a6bd57"},{"cell_type":"code","source":"Derived d; // Create an instance of Derived\nBase* ptr = &d; // Pointer to Base type, pointing to Derived instance\n\nptr->hello(); // Virtual dispatch ensures Derived::hello() is called","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":10,"outputs":[{"name":"stdout","output_type":"stream","text":"Hallo\n"}],"id":"cec959c3"},{"cell_type":"markdown","source":"##
Discussion
\nIs there another way to call the function? Think about what we have learned in the past two weeks. \n\n**HINT:** the above example called the function by using a pointer, what we have learned at the same time with pointer?","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"15ce23b1"},{"cell_type":"markdown","source":"##
Discussion
\nIs there another way to call the function? Think about what we have learned in the past two weeks.\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"a7572a57"},{"cell_type":"code","source":"Derived d; // Create an instance of Derived\nBase& ref = d; // Reference to Base type, referring to Derived instance\n\nref.hello();","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":12,"outputs":[{"name":"stdout","output_type":"stream","text":"Hallo\n"}],"id":"a85fe520"},{"cell_type":"markdown","source":"##
Quiz var1: Guess what happens
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d9b1a196"},{"cell_type":"markdown","source":"```c++\nstruct Base {\n virtual void hello() const = 0;\n};\nstruct Derived : public Base {\n void hello() const\n {\n std::cout << “Hallo\\n”;\n }\n};\n```","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"85060d6d"},{"cell_type":"markdown","source":"##
Quiz var1: Guess what happens
\n```c++\nstruct Base {\n virtual void hello() const = 0;\n};\nstruct Derived : public Base {\n void hello() const\n {\n std::cout << “Hallo\\n”;\n }\n};\n```\n- Structurally correct in terms of C++ syntax. \n- Derived properly overrides the pure virtual function hello() from Base, allowing instances of Derived to be created and used polymorphically through pointers or references to Base.","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"eab2cb58"},{"cell_type":"markdown","source":"##
Quiz var2: Guess what happens
\n\n```c++\nstruct Base {\n virtual void hello() const {\n std::cout << \"Hello\\n\";\n }\n};\nstruct Derived : public Base {\n void hallo() const //Good luck with debugging\n {\n std::cout << \"Hallo\\n\";\n }\n};\n```","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"64ba0dbe"},{"cell_type":"markdown","source":"##
Quiz var2: Guess what happens
\n\n```C++\nstruct Base {\n virtual void hello() const {\n std::cout << \"Hello\\n\";\n }\n};\nstruct Derived : public Base {\n void hallo() const //Good luck with debugging\n {\n std::cout << \"Hallo\\n\";\n }\n};\n```\n- The Derived struct inherits from Base and introduces a new function hallo() that prints \"Hallo\\n\" to the standard output. \n- However, Derived does not override the hello() function from Base; it simply provides an additional function.","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"df07cac2"},{"cell_type":"markdown","source":"##
The override keyword
\nC++11 introduces the override keyword to explicitly state\nthat a function from the base class shall be overridden\n

Modify the program below yourself to see the different with Quiz var2

","metadata":{"slideshow":{"slide_type":"slide"}},"id":"10d391bb"},{"cell_type":"code","source":"struct Base {\n virtual void hello() const = 0;\n virtual ~Base() {} // virtual destructor for proper cleanup\n};\nstruct Derived : public Base {\n void hello() const override {std::cout << \"Hallo\\n\";}\n};\n\nDerived d; // Create an instance of Derived\nd.hello(); // Calls the overridden hello() method in Derived\nBase* b = &d; // Since hello() is a virtual function, you can also call it through a Base pointer\nb->hello(); // This will also call Derived::hello()\n //NOTE: If the function to be overridden does not exist in the base class an error is thrown by the compiler","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":15,"outputs":[{"name":"stdout","output_type":"stream","text":"Hallo\nHallo\n"}],"id":"220c1391"},{"cell_type":"markdown","source":"##
The override keyword
\nThe overriding function must exactly match the signature of the function from the base class\n\nstruct Base {\nvirtual void hello() const { std::cout << “Hello\\n”; }\n};\n\nstruct Derived : public Base {\nvoid hello() const override { std::cout << “Hallo\\n”; }\n};\n\n~~struct Derived2 : public Base {\nvoid hello() override { std::cout << “Hallo\\n”; }\n};~~","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"3cc60aad"},{"cell_type":"markdown","source":"##
The final keyword
\nC++11 introduces the final keyword to explicitly state that a\nfunction from the base class must not be overridden\n\nstruct Base {\nvirtual void hello() const final { std::cout << “Hello\\n”; } };\n\nIf a derived class tries to override the function hello an error is thrown by the compiler","metadata":{"slideshow":{"slide_type":"slide"}},"id":"1580c2c0"},{"cell_type":"markdown","source":"##
The override and final keywords
\nThe override and final keywords are often used together\n\nstruct Base {\n // Pure virtual function must be overridden in a derived class\n virtual void hello() const = 0;\n };\n struct Derived : public Base {\n // Override pure virtual function and make it final\n void hello() const override final\n { std::cout << “Hello\\n”; }\n };\n struct Derived2 : public Derived {\n // Must not try to override function hello\n};","metadata":{"slideshow":{"slide_type":"slide"}},"id":"59198f71"},{"cell_type":"markdown","source":"##
Overloading vs. overriding
\n
Implementation of method or operator with identical name but a different interface, e.g.\n

double operator*(const\nVector & other) const {...}

\n

overloads

\n

double operator*(double\n other) const {...}

\n
\n\n
Implementation of method or operator with identical name and the same interface in a derived class or structure, e.g.\n

struct Derived : public Base {\n void hello(...) const override {...}\n};

\n

overrides

\n

struct Base {\n virtual void hello(...) const {...};\n};

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"dc02afa6"},{"cell_type":"markdown","source":"##
Overloading vs. overriding
\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"bba079e0"},{"cell_type":"markdown","source":"##
Implementation time! Task: Calculator
\nWrite a class (or for demonstrating purposes a hierarchy of classes) that provide(s) a member function to calculate the **sum of two and three integer values,** respectively\n- Use *static* polymorphism: method overloading\n- Use *dynamic* polymorphism: method overriding","metadata":{"slideshow":{"slide_type":"slide"}},"id":"29c787e8"},{"cell_type":"markdown","source":"##
Static polymorphism
\nMethod overloading (at compile time), the StaticCalc has two member functions with identical names but different interface; compiler can decide at compile time which of the two functions should be used","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"85357aad"},{"cell_type":"code","source":"struct StaticCalc {\n int sum(int a, int b) const {\n return a + b;\n }\n // Overloaded function to sum three integers\n int sum(int a, int b, int c) const { return sum(sum(a,b),c);}\n};\n\nStaticCalc statC;\nstd::cout << \"Static sum of two integers: \" << statC.sum(1, 2) << std::endl;\nstd::cout << \"Static sum of three integers: \" << statC.sum(1, 2, 3) << std::endl;","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"b9317436"},{"cell_type":"markdown","source":"##
Static polymorphism
\nMethod overloading (not working!)\n\nstruct StaticCalcBug {\nint sum(int a, int b) { return a+b; }\nvoid sum(int a, int b) { std::cout<< a+b <<std::endl;}\n};\n- Difference must be in the interface of the arguments passed. Same name with different parameter lists is allowed (either the number of parameters or their types must differ).\n- You cannot have two functions that only differ by their return type. Because the return type could be cast into another type","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"3fd89e64"},{"cell_type":"markdown","source":"##
Static polymorphism
\nMethod overloading (also not working!)\n\nstruct StaticCalcBug2 {\n int sum(int a, int b) { return a+b; }\n int sum(int c, int d) { return c+d; }\n };\n\n- The interface only refers to the types and not to the names","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d1f9b947"},{"cell_type":"markdown","source":"##
Static polymorphism - Laaaaast! counter example
\nMethod overloading (still not working!)\n\nstruct StaticCalcBugLast {\n int sum(int a, int b) { return a+b; }\n int sum(int a, int b, int c=1) { return a+b+c; }\n };\n\n- Be careful with default values. Here, the compiler cannot decide unambiguously which of the two functions should be used in the case sum(arg0, arg1)","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"1bc05ee8"},{"cell_type":"markdown","source":"##
Rule of thumb:
\nIf it is not crystal clear to you which function should be used then the compiler will fail, too.\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"b608b4b4"},{"cell_type":"markdown","source":"##
Static polymorphism (master version1)
\nMethod overloading: decision about which method to call is made at compile time; hence the compiler can decide to inline code to improve performance (no overhead due to\nfunction calls and copying of data to and from the stack!)\n\n```C++\nstd::cout << statC.sum(1,2) << std::endl;\nstd::cout << statC.sum(1,2,3) << std::endl;\n``` \n becomes\n \n```C++\nstd::cout << (1+2) << std::endl;\nstd::cout << ((1+2)+3) << std::endl;\n```","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"0078e458"},{"cell_type":"markdown","source":"##
Static polymorphism (master version2)
\nMethod overloading: since static polymorphism takes place at compile time, the inline specifier can be used to explicitly ‘suggest’ to the compiler to inline the function\n\nstruct StaticCalcMaster {\n inline int sum(int a, int b)\n { return a+b; }\n inline int sum(int a, int b, int c)\n { return sum(sum(a,b),c); }\n };","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"3f4ddb3a"},{"cell_type":"markdown","source":"##
Dynamic polymorphism
\nMethod overloading: reimplementation of a function inherited\nfrom a base class with new function body and same interface!","metadata":{"slideshow":{"slide_type":"slide"}},"id":"1f1852a6"},{"cell_type":"code","source":"struct BaseCalc {\n virtual int sum2(int a, int b) { return a+b; }\n int sum3(int a, int b, int c)\n { return sum2(sum2(a,b),c); }\n };\nstruct DerivedCalc : public BaseCalc {\n int sum2(int a, int b) override final { return b+a; }\n};","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"},"trusted":true},"execution_count":1,"outputs":[],"id":"044be82b"},{"cell_type":"code","source":"BaseCalc baseCalc;\nDerivedCalc derivedCalc;\n// Using the base class to sum two numbers\nstd::cout << \"BaseCalc sum2: \" << baseCalc.sum2(1, 2) << std::endl; \n// Using the base class to sum three numbers\nstd::cout << \"BaseCalc sum3: \" << baseCalc.sum3(1, 2, 3) << std::endl; \n// Using the derived class to sum two numbers, this will use the overridden method\nstd::cout << \"DerivedCalc sum2: \" << derivedCalc.sum2(1, 2) << std::endl;\n// Using the derived class to sum three numbers, this will use the base\n// class method sum3, which in turn will use the overridden sum2 method\n// from DerivedCalc\nstd::cout << \"DerivedCalc sum3: \" << derivedCalc.sum3(1, 2, 3) << std::endl; ","metadata":{"tags":[],"slideshow":{"slide_type":"subslide"},"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"BaseCalc sum2: 3\nBaseCalc sum3: 6\nDerivedCalc sum2: 3\nDerivedCalc sum3: 6\n","output_type":"stream"}],"id":"7e53ac75"},{"cell_type":"markdown","source":"##
Dynamic polymorphism(where things can go wrong)
\nMethod overloading: a common pitfall is to forget the virtual specifier in the base class to indicate that the sum2 function from the base class can be overridden in a derived class\n\nstruct BaseCalc {\n int sum2(int a, int b) { return a+b; }\n int sum3(int a, int b, int c){ return sum2(sum2(a,b),c); }\n };\n struct DerivedCalc : public BaseCalc {\n int sum2(int a, int b) { return b+a; }\n};","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"5a1aba0e"},{"cell_type":"markdown","source":"##
Dynamic polymorphism(where things can go wrong)
\nTake home notes, read after the lecture.\nHere's what happens if you forget vitual specifier\n- Method Hiding: In DerivedCalc, the sum2 method hides the sum2 method from BaseCalc. This means that if you have an object of DerivedCalc and call sum2, it will use DerivedCalc's sum2. However, if you have a pointer or reference to BaseCalc that actually points to a DerivedCalc object and call sum2, it will still use BaseCalc's sum2. This is because the method is not virtual and hence does not support polymorphic behavior.\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"1a10cd6d"},{"cell_type":"markdown","source":"- No Polymorphism: Without the virtual keyword in BaseCalc, sum2 in DerivedCalc doesn't exhibit polymorphic behavior, crucial for method overriding in C++.\n\nRule of thumb: These bugs are hard to find (they often remain unrecognised) and can be prevented by using the override keyword in C++11. It explicitly indicates that a function is intended to override a virtual function in a base class.","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d3e4681a"},{"cell_type":"markdown","source":"##
Dynamic polymorphism - general suggestions
\nMethod overriding: decision about which virtual method to call is made at run time; hence inlining is not possible\n\nCommon design pattern\n- Specify expected minimal functionality of a group of classes in\nabstract base class via pure virtual member functions\n- Implement generic common functionality of a group of classes in\nabstract base class via virtual member functions\n- Implement specific functionality of a particular derived class by overriding the pure virtual member function","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"6922d1ff"},{"cell_type":"markdown","source":"##
Example: inner product space
\nIn linear algebra, an inner product space is a vector space $V$ that is equipped with a special mapping (inner product)\n$$\\langle\\cdot, \\cdot\\rangle: V \\times V \\rightarrow \\mathbb{R}\\text{ or }\\mathbb{C}$$\nInner product spaces have a naturally induced norm\n$$\\|x\\|=\\sqrt{\\langle x, x\\rangle}$$","metadata":{"slideshow":{"slide_type":"slide"}},"id":"c8e2962e"},{"cell_type":"markdown","source":"##
Example: inner product space
\nClass InnerProductSpaceBase declares inner product as pure\nvirtual and implements the naturally induced norm\n\nstruct InnerProductBase\n{\n virtual double inner_product(... x,... y) = 0;\n double norm(... x) { return inner_product(x,x); } \n};\n\nDerived InnerProductSpace class implements inner product\n\nstruct InnerProductSpace : public InnerProductSpaceBase {\n double inner_product(... x, ... y) = { return x*y; }};","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"a33f6fa6"},{"cell_type":"markdown","source":"##
Implementation time! Task: Calculator2
\nExtend the calculator class so that it can handle numbers of integer, float and double type at the same time\n\n- Prevent manual code duplication \n- Prevent explicit type casting\n- Make use of auto-functionality (C++11/14/17) \n- Make use of template meta programming","metadata":{"slideshow":{"slide_type":"slide"}},"id":"ea9e50bd"},{"cell_type":"markdown","source":"##
Vanilla implementation in C++
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5ccb14af"},{"cell_type":"code","source":"#include \nstruct Calc2 {\n int sum(int a, int b) { return a + b; }\n int sum(int a, int b, int c) { return sum(sum(a, b), c); }\n};\nCalc2 calculator;\n// Using the sum method with two arguments\nstd::cout << \"Sum of 1 and 2: \" << calculator.sum(1, 2) << std::endl;\n// Using the sum method with three arguments\nstd::cout << \"Sum of 1, 2, and 3: \" << calculator.sum(1, 2, 3) << std::endl;","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"},"trusted":true},"execution_count":9,"outputs":[{"name":"stdout","text":"Sum of 1 and 2: 3\nSum of 1, 2, and 3: 6\n","output_type":"stream"}],"id":"3ca269a9"},{"cell_type":"markdown","source":"##
Automatic return type deduction (C++11)
\n- Explicit definition of the function return type\n\nint sum(int a, int b){ return a+b; }\n\n- Automatic function return type (since C++11)\n\nauto sum(int a, int b) -> decltype(a+b)\n { return a+b; }\n- By using decltype, the return type of the sum function is determined automatically as the type of operator+(a,b)","metadata":{"slideshow":{"slide_type":"slide"}},"id":"df8b9e7b"},{"cell_type":"markdown","source":"##
Automatic return type deduction
\ndecltype specifier (C++11) queries the type of an expression","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"ccc39c6b"},{"cell_type":"code","source":"#include \nstruct Calc2 {\n auto sum(int a, int b) -> decltype(a + b) {return a + b;}\n auto sum(int a, int b, int c) -> decltype(sum(sum(a, b), c)) {\n return sum(sum(a, b), c);\n }\n};\n\nCalc2 C;\nstd::cout << C.sum(1, 2) << std::endl; // Outputs: 3\nstd::cout << C.sum(1, 2, 3) << std::endl; // Outputs: 6","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"},"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"3\n6\n","output_type":"stream"},{"execution_count":11,"output_type":"execute_result","data":{"text/plain":"@0x7f600261cde0"},"metadata":{}}],"id":"7d312541"},{"cell_type":"markdown","source":"##
Automatic type deduction (C++14)
\nC++14 deduces the type of parameters automatically\n\nauto sum(int a, int b) // no -> decltype(...) needed\n { return a+b; }\nauto sum(int a, int b, int c) // no -> decltype(...) needed\n { return sum(sum(a,b),c); }\n \nRemark: This C++14 feature helps to improve readability of the code and prevents deduction errors (due to forgotten/ inconsistent deduction rule by the programmer) but it does not solve the problem of being able to pass arguments of different types to the same function","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"b6666835"},{"cell_type":"markdown","source":"##
Function templates
\nTemplate meta programming is the standard technique to deal with arbitrary (= generic) function parameters\n\nFunction templates: allow you to implement so-called parameterized functions for generic parameter types\n\ntemplate<typename R, typename A, typename B> \nR sum(A a, B b)\n{return a+b; }","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5c04f99f"},{"cell_type":"markdown","source":"##
Function templates
\nTypes must be specified explicitly when the function is called\n\nint s1 = sum< int, int, int>(1, 2);\ndouble s2 = sum<double, double, int>(1.2, 2); \ndouble s3 = sum<double, float, double>(1.4, 2.2);\n\nThis can be slightly simplified by using the auto specifier\n\nauto s1 = sum< int, int, int>(1, 2);\nauto s2 = sum<double, double, int>(1.2, 2); \nauto s3 = sum<double, float, double>(1.4, 2.2);\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"aed51a91"},{"cell_type":"markdown","source":"##
Function templates
\nC++11: automatic return type deduction\n\ntemplate< typename A, typename B>\nauto sum(A a, B b) –> decltype(a+b){ return a+b; }\n\nC++14: automatic type deduction\n\ntemplate< typename A, typename B>\nauto sum(A a, B b){ return a+b; }\n\nUsage\n\nauto s1 = sum<int, int>(1, 2);","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"e60a2c69"},{"cell_type":"markdown","source":"##
Function templates
\nHow to convert this function into a templated function\n\n```C++\nint sum(int a, int b, int c)\n{\n return sum(sum(a,b), c);\n }\n``` ","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"9551ad42"},{"cell_type":"markdown","source":"##
Function templates
\nUse explicit return type parameter (ugly!) -> NO\ntemplate<typename R, typename A, typename B, typename C>\nauto sum(A a, B b, C c)\n {\n return sum<R,C>(sum<A,B>(a,b), c);\n }\nGuess what this function call will return\n\nauto s1 = sum< int, double,double,double>(1.1,2.2,3.3)","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"4c93950a"},{"cell_type":"markdown","source":"##
Function templates
\nUse a smart combination of templates and auto specifier\n\ntemplate<typename A, typename B>\nauto sum(A a, B b) -> decltype(a+b) // omit in C++14\n{return a+b; }\n\n\ntemplate<typename A, typename B, typename C> \nauto sum(A a, B b, C c)\n{\n return sum<decltype(sum<A,B>(a,b)),C> (sum<A,B>(a,b), c);\n}","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"cdc02ef1"},{"cell_type":"markdown","source":"##
Function templates
\nBut wait, C++ can deduce the type of the template\nargument from the given argument automatically\n\ntemplate<typename A, typename B>\nauto sum(A a, B b) -> decltype(a+b) // omit in C++14\n{return a+b; }\ntemplate<typename A, typename B, typename C> \nauto sum(A a, B b, C c)\n{\n return sum(sum(a,b), c);\n}","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"7b01af98"},{"cell_type":"markdown","source":"##
Function templates
\nWe can call the generic sum functions as follows:\n\nauto s1 = sum<int, int> (1, 2);\nauto s2 = sum<double, int> (1.2, 2);\nauto s3 = sum<float, double>(1.4f, 2.2);\n\nOr, even simpler, as follows:\n\nauto s1 = sum (1, 2);\nauto s2 = sum (1.2, 2);\nauto s3 = sum (1.4f, 2.2)","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"a40ccf72"},{"cell_type":"markdown","source":"##
Function templates
\n- Since the compiler needs to duplicate code and substitute A,B,C for each combination of templated types both the compile time and the size of the executable will increase\n- Template meta programming is simplest if the code resides in header files only; later we will see how to use template meta-programming together with pre-compiled libraries","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"01ad453e"},{"cell_type":"markdown","source":"##
Implementation time! Task: generic Vector class
\nWrite a Vector class that can store real values and complex values and supports the following operations:\n- Addition of two vectors of the same length\n- Multiplication of a vector with a scalar\n- Dot product of a vector with another one\nIn all operations, the involved objects (vectors/scalar) can be of different types, e.g., double/float","metadata":{"slideshow":{"slide_type":"slide"}},"id":"faab6efb"},{"cell_type":"markdown","source":"##
Vector class prototype
\nImplementation of Vector-of-double class","metadata":{"slideshow":{"slide_type":"slide"}},"id":"e2f214ca"},{"cell_type":"code","source":"/*\n* Unable to merge this cell for the presentation if you wanna run it interactively, \n* please exit the presentation mode to show the whole program.\n*/\n#include \nclass Vector{\n private:\n double* data;\n int n;\n public:\n//----------------------------------- Constructors below --------------------------------------------------//\n \n Vector() : n(0), data(nullptr) {} // Default constructor\n \n Vector(int n) : n(n), data(new double[n]) {} // Constructor with size\n \n Vector(const Vector& other) : n(other.n), data(new double[other.n]) {\n std::copy(other.data, other.data + n, data);\n } // Copy constructor\n \n ~Vector() {\n delete[] data;\n }// Destructor\n \n//----------------------------------- method and operators --------------------------------------------------//\n \n // Addition (+=) operator\n Vector& operator+=(const Vector& rhs) {\n if (n != rhs.n) {\n throw std::invalid_argument(\"Vectors must be of the same size\");\n }\n for (int i = 0; i < n; i++) {\n data[i] += rhs.data[i];\n }\n return *this;\n }\n \n // Scalar multiplication (*=) operator\n Vector& operator*=(double scalar) {\n for (int i = 0; i < n; i++) {\n data[i] *= scalar;\n }\n return *this;\n }\n \n // Dot product\n double dot(const Vector& rhs) const {\n if (n != rhs.n) {\n throw std::invalid_argument(\"Vectors must be of the same size\");\n }\n double sum = 0.0;\n for (int i = 0; i < n; i++) {\n sum += data[i] * rhs.data[i];\n }\n return sum;\n }\n//-------------------------Utility for printing (for demonstration purposes)---------------------------------// \n void print() const {\n std::cout << \"[\";\n for (int i = 0; i < n; i++) {\n std::cout << data[i] << (i < n - 1 ? \", \" : \"\");\n }\n std::cout << \"]\" << std::endl;\n }\n \n};\n\n//----------------------------------- main function below --------------------------------------------------//\nVector v1(3);\nVector v2(3);\n\n// Example usage\nfor (int i = 0; i < 3; i++) {\n v1[i] = i + 1; // 1, 2, 3\n v2[i] = i * 2; // 0, 2, 4\n}\n\nv1 += v2;\nv1.print(); // Should print [1, 4, 7]\n\n// v1 *= 2;\n// v1.print(); // Should print [2, 8, 14]\n\n// std::cout << \"Dot product: \" << v1.dot(v2) << std::endl; // Should print 48","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"},"trusted":true},"execution_count":null,"outputs":[],"id":"2d40162c"},{"cell_type":"markdown","source":"##
Brainstorming
\nFunction templates alone will not help since the type of a class attribute needs to be templated -> class templates\n\nSome member functions can be implemented generically, e.g., addition of two vectors and multiplication of a vector with a scalar value since they are the same for all types\n\nSome member functions must be implemented in different manners for real and complex values ->specialisation\n\n$$x \\cdot y=\\sum_{i=1}^n x_i y_i, \\quad x, y \\in \\mathbb{R}, \\quad x \\cdot y=\\sum_{i=1}^n x_i \\bar{y}_i \\quad x, y \\in \\mathbb{C}$$","metadata":{"slideshow":{"slide_type":"slide"}},"id":"359d5141"},{"cell_type":"markdown","source":"##
Class template: anything vector class
\nImplementation of Vector-of-anything class -- creating a generic vector that can store elements of any type. \n\nCAUTION: We are going to leave the presentation mode to have an overview of the entire program (without the main function).\n","metadata":{"slideshow":{"slide_type":"slide"}},"id":"8297b9e9"},{"cell_type":"code","source":"#include \n#include \n\ntemplate\nclass Vector {\nprivate:\n T* data;\n int n;\n\npublic:\n//--------------------------------------Constructors below-----------------------------------------//\n // Default constructor\n Vector() : data(nullptr), n(0) {}\n\n // Constructor with size\n Vector(int n) : n(n), data(new T[n]()) {}\n\n // Destructor\n ~Vector() {\n delete[] data;\n }\n\n//----------------------End of constructors, go to next page for explaination----------------------//\n \n\n//------------------------------------Methods and operators below-----------------------------------------// \n // Addition (+=) operator\n Vector& operator+=(const Vector& other) {\n if (n != rhs.n) {\n throw std::invalid_argument(\"Vectors must be of the same size\");\n }\n for (int i = 0; i < n;i++) {\n data[i] += other.data[i];\n }\n return *this;\n }\n \n // Scalar multiplication (*=) operator\n Vector& operator*=(T scalar) {\n for (int i = 0; i < n; i++) {\n data[i] *= scalar;\n }\n return *this;\n }\n \n // Dot product\n T dot(const Vector& other) const {\n if (n != rhs.n) {\n throw std::invalid_argument(\"Vectors must be of the same size\");\n }\n T sum = 0;\n for (int i = 0; i < n; i++) {\n sum += data[i] * other.data[i];\n }\n return sum;\n }\n//------------------Combined class template and function template below------------------------// \n template\n Vector& operator*=(S scalar){\n for (auto i=0; iClass template\nTemplate parameter must be explicitly specified\n\nVector<int> x(10); // Vector-of-int with length 10\nVector<double> y; // Empty Vector-of-double\nVector<float> z(5); // Vector-of-float with length 5 \n\n\nRemark: if you want to pass a Vector-of-anything to a function in the templated class Vector you have to write\n\nVector<T> v\nVector<T>& v\n\n\ninstead of \n\nVector v\nVector& v\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d064f346"},{"cell_type":"markdown","source":"##
Class template
\nWith the **class template parameter T** we can do","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"cc5be4cb"},{"cell_type":"code","source":"int main() {\n Vector x1(5), x2(5);\n\n // Initialize x1 and x2 with some values\n for (int i = 0; i < 5; ++i) {\n x1[i] = i + 1; // x1 = [1, 2, 3, 4, 5]\n x2[i] = 2 * (i + 1); // x2 = [2, 4, 6, 8, 10]\n }\n\n x1 += x2; // x1 = [3, 6, 9, 12, 15]\n x1 *= 2; // x1 = [6, 12, 18, 24, 30]\n int i = x1.dot(x2); // Dot product of x1 and x2\n\n std::cout << \"Dot product: \" << i << std::endl; // Output the dot product\n return 0;\n}","metadata":{"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"fa572e28"},{"cell_type":"markdown","source":"How about?\n\nVector<int> x2(5);\nx2 *= (double)1.2;","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d2d728f9"},{"cell_type":"markdown","source":"##
Intermezzo
\n\ntemplate<typename T>\nclass Vector {\n...\n template<typename S>\n Vector<T>& operator*=(S scalar)\n {\n for (auto i=0; i<n; i++)\n data[i] *= scalar;\n return *this;\n }\n};\n","metadata":{"slideshow":{"slide_type":"slide"}},"id":"924586b6"},{"cell_type":"markdown","source":"##
Intermezzo
\nAt first glance, this seems to be more flexible\n\nVector<double> x1(5); x1 = x1*(int)(5);\n\nBut be really careful since strange things can happen\n\nVector<int> x1(5); x1 = x1*(double)(5);\n\nRule of thumb: before using extensive template meta programming like this think about all(!) implications","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"1ee4a135"},{"cell_type":"markdown","source":"##
Specialization
\nThe dot product needs special treatment since\n\nT dot(const Vector<T>& other) const\n {\n T d=0;\n for (auto i=0; i<n; i++)\n d += data[i]*other.data[i];\n return d; }\n- Lacks the complex conjugate of other and yields the wrong return type in case of complex-valued vectors\n\nRemedy: implement a specialised variant for this case","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"040d2875"},{"cell_type":"markdown","source":"##
Specialization
\nGeneric dot product implemented in Vector class\n\n#include <complex>\ntemplate<typename T>\nclass Vector { ...\n T dot(const Vector<T>& other) const {...}\n};\n\nThis function is used whenever no specialized implementation for a concrete type is available","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"3ca84f3f"},{"cell_type":"markdown","source":"##
Specialization
\n Specialized dot product for Vectors-of-complex-complex\n \ntemplate<>\nstd::complex<float> Vector< std::complex<float> >::\n dot(const Vector< std::complex<float>>& other) const {\n std::complex<float> d=0;\n // special treatment of dot product\n for (auto i=0; i<n; i++)\n d += data[i]*std::conj(other.data[i]);\n return d;\n}","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"12068121"},{"cell_type":"markdown","source":"##
Specialization
\n\nCurrent implementation yields\n\nVector<float> x1(5), x2(5);\nauto x1.dot(x2); // calls generic implementation\n\nVector<std::complex<float> x1(5), x2(5);\nauto x1.dot(x2); // calls generic implementation\n\nVector<std::complex<double> > z1(5), z2(5);\nauto z1.dot(z2); // calls generic implementation\n\nauto x1.dot(y1); // does not even compile","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"c748a549"},{"cell_type":"markdown","source":"##
Outlook on next session
\n\nC++ allows you to **partially specialize** class templates\n\ntemplate<typename S>\nstd::complex<S> Vector<std::complex<S> >::\n dot(const Vector<std::complex<S> > other) const { \n std::complex<S> d=0;\n for (auto i=0; i<n; i++)\n d += data[i]*std::conj(other.data[i]);\n return d;\n}\n\nNote that this code will not compile. We will see why and learn remedies. Welcome to where template magic begins!","metadata":{"slideshow":{"slide_type":"slide"}},"id":"4de0e0b9"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"1b56e42e"}]} \ No newline at end of file diff --git a/notebooks/plots/lecture3cat.gif b/notebooks/plots/lecture3cat.gif index 2882004..b42b4b6 100644 Binary files a/notebooks/plots/lecture3cat.gif and b/notebooks/plots/lecture3cat.gif differ