From e9cea1a4f3fda0086cb28b91eb94badde5cc2b7b Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Mon, 12 Dec 2022 14:47:19 +0100 Subject: [PATCH] Perform corrections and add new stuff Commit adds an example of the extra costs which might result from explicit template instantiation when it occurs in multiple compilation units. --- notebooks/10_Polymorphic_Classes.ipynb | 865 +++++++++++++++++++++++- notebooks/11_Template_Basics.ipynb | 2 +- notebooks/12_Template_Basics_cont.ipynb | 2 +- 3 files changed, 866 insertions(+), 3 deletions(-) diff --git a/notebooks/10_Polymorphic_Classes.ipynb b/notebooks/10_Polymorphic_Classes.ipynb index c8b24b3..9b8d19b 100644 --- a/notebooks/10_Polymorphic_Classes.ipynb +++ b/notebooks/10_Polymorphic_Classes.ipynb @@ -1 +1,864 @@ -{"metadata":{"orig_nbformat":4,"kernelspec":{"display_name":"C++14","language":"C++14","name":"xcpp14"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"14"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Polymorphic or Virtual Classes","metadata":{},"id":"1a6233be"},{"cell_type":"markdown","source":"## Virtual Functions","metadata":{},"id":"b6f1b161-e13c-4f2f-bb7c-9b2ecbdb8c1a"},{"cell_type":"markdown","source":"We return to our example from <a href=\"https://www.hanser-kundencenter.de/fachbuch/artikel/9783446458468\">[Go19]</a> and extend it a little.","metadata":{},"id":"7ec80016"},{"cell_type":"code","source":"#include <iostream>\n\nclass person {\n\npublic:\n person( const std::string& name ) : name(name) {};\n\n void all_info() const {\n std::cout << \"[person] My name is \" << name << std::endl;\n }\n\nprotected:\n std::string name;\n};","metadata":{"trusted":true},"execution_count":25,"outputs":[],"id":"7942848b"},{"cell_type":"code","source":"class student : public person {\n\npublic:\n student( const std::string& name, const std::string program ) :\n person(name), program(program) {}\n\n void all_info() const {\n std::cout << \"[student] My name is \" << name\n << \", I study \" << program << std::endl;\n }\n\nprivate:\n std::string program;\n};","metadata":{"trusted":true},"execution_count":26,"outputs":[],"id":"f40024e2"},{"cell_type":"code","source":"class researcher : public person {\n\npublic:\n researcher( const std::string& name, const std::string& field ) :\n person(name), field(field) {}\n\n void all_info() const {\n std::cout << \"[researcher] My name is \" << name\n << \", I work on problems in \" << field << std::endl;\n }\n\nprivate:\n std::string field;\n};","metadata":{"trusted":true},"execution_count":27,"outputs":[],"id":"459a1ce0"},{"cell_type":"markdown","source":"As `student`s and `researcher`s are `person`s we can e.g. generate a vector of person pointers, where each pointer targets a different university member.","metadata":{},"id":"c5c9fb7a"},{"cell_type":"code","source":"#include <vector>\n\nstudent joe( \"Joe\", \"Geophysics\" );\nstudent jane( \"Jane\", \"Geology\" );\n\nresearcher erika( \"Erika\", \"Seismology\" );\nperson franz( \"Franz\" );\n\nstd::vector<person*> uniMembers;\n\nuniMembers.push_back( &joe );\nuniMembers.push_back( &jane );\nuniMembers.push_back( &erika );\nuniMembers.push_back( &franz );","metadata":{"trusted":true},"execution_count":28,"outputs":[],"id":"5ff4684f"},{"cell_type":"markdown","source":"* Because of inheritance, we can now have all pointers to our different people in the same vector, although the targeted objects are of different type.\n* Problem is, when we work with the vector entries and ask for info, we only get the minimal stuff from the base class ...","metadata":{},"id":"56cad921"},{"cell_type":"code","source":"for( auto memb: uniMembers ) memb->all_info();","metadata":{"trusted":true},"execution_count":29,"outputs":[{"name":"stdout","text":"[person] My name is Joe\n[person] My name is Jane\n[person] My name is Erika\n[person] My name is Franz\n","output_type":"stream"}],"id":"aebeaf35"},{"cell_type":"markdown","source":"... instead of the full records, such as","metadata":{},"id":"3f7ae736"},{"cell_type":"code","source":"jane.all_info();\nerika.all_info();","metadata":{"trusted":true},"execution_count":30,"outputs":[{"name":"stdout","text":"[student] My name is Jane, I study Geology\n[researcher] My name is Erika, I work on problems in Seismology\n","output_type":"stream"}],"id":"445c4d28"},{"cell_type":"markdown","source":"The solution to this is to make our classes **polymorphic**.\n\n***\n### Definition \nA class that contains at least one **virtual function** is called **polymorphic** or **virtual**.\n\n***","metadata":{},"id":"977efc72"},{"cell_type":"markdown","source":"Let us change our example by making `all_info()` a virtual function.","metadata":{},"id":"c0f70535-8833-42c4-b1ea-fd5dcdca72f2"},{"cell_type":"code","source":"#include <iostream>\n\nclass person {\n\npublic:\n person( const std::string& name ) : name(name) {};\n\n virtual void all_info() const { // <- one small change only ;-)\n std::cout << \"[person] My name is \" << name << std::endl;\n }\n\nprotected:\n std::string name;\n};\n\nclass student : public person {\n\npublic:\n student( const std::string& name, const std::string program ) :\n person(name), program(program) {}\n\n void all_info() const {\n std::cout << \"[student] My name is \" << name\n << \", I study \" << program << std::endl;\n }\n\nprivate:\n std::string program;\n};\n\nclass researcher : public person {\n\npublic:\n researcher( const std::string& name, const std::string& field ) :\n person(name), field(field) {}\n\n void all_info() const {\n std::cout << \"[researcher] My name is \" << name\n << \", I work on problems in \" << field << std::endl;\n }\n\nprivate:\n std::string field;\n};","metadata":{"trusted":true},"execution_count":31,"outputs":[],"id":"4b596bd2"},{"cell_type":"code","source":"#include <vector>\n\nstudent joe( \"Joe\", \"Geophysics\" );\nstudent jane( \"Jane\", \"Geology\" );\n\nresearcher erika( \"Erika\", \"Seismology\" );\nperson franz( \"Franz\" );\n\nstd::vector<person*> uniMembers;\n\nuniMembers.push_back( &joe );\nuniMembers.push_back( &jane );\nuniMembers.push_back( &erika );\nuniMembers.push_back( &franz );\n\nfor( auto memb: uniMembers ) memb->all_info();","metadata":{"trusted":true},"execution_count":32,"outputs":[{"name":"stdout","text":"[student] My name is Joe, I study Geophysics\n[student] My name is Jane, I study Geology\n[researcher] My name is Erika, I work on problems in Seismology\n[person] My name is Franz\n","output_type":"stream"}],"id":"12bebbf9"},{"cell_type":"markdown","source":"#### Explanation \nSo what is the difference here? Whenever we work with a **pointer** `pVar` or a **reference** `rVar` to a class and invoke a function `myFunc()` via that on the target the compiler will check the following:\n\n1. What is the **static type** of `pVal` and or `pVar`, i.e. how were they declared?\n1. Does that class have a method `myFunc()`?\n1. Is `myFunc()` accessible, i.e. public, or not, i.e. private?\n1. Is it a **virtual function**?\n * No: Then invoke it.\n * Yes: Check what the **dynamic type** is of `pVal` or `pVar`, i.e. the type of the object they target.\n Then invoke `myFunc()` for that type.","metadata":{},"id":"ef04a619"},{"cell_type":"markdown","source":"#### Remark 1 \nThis only works for pointers and references, but not for variables, even if they are initialised or copy constructed from a child object.","metadata":{},"id":"ae46b4e3"},{"cell_type":"code","source":"person alias{joe};\nalias.all_info();","metadata":{"trusted":true},"execution_count":33,"outputs":[{"name":"stdout","text":"[person] My name is Joe\n","output_type":"stream"}],"id":"f371e175-cbc1-45e4-b8bb-06b8ebc92ef1"},{"cell_type":"markdown","source":"Only the parts of `joe` that belong to its `person` base class are used for the initialisation of `alias`. This is sometimes refered to as **slicing**.","metadata":{},"id":"2d6da352"},{"cell_type":"markdown","source":"#### Remark 2 \nThe decision which version of `all_info()` must be invoked **cannot** be made by the compiler, as it may not have the necessary information on the type of the target of a pointer or reference. It can only be made at **run-time**.","metadata":{},"id":"ad22ac97"},{"cell_type":"markdown","source":"### Terminology\nSince the decision which virtual method is invoked on a pointer or reference to a polymorphic object is performed at run-time, one calls this **late** or **dynamic binding**.\n\nIt constitutes a form of **dynamic polymorphism**, as opposed to the static polymorphism of overloading or templates, which get's resolved at compile-time.","metadata":{},"id":"bcb61161"},{"cell_type":"markdown","source":"### Costs\nIn order to be able to correctly dispatch the virtual function call at runtime, the compiler generates a <a href=\"https://en.wikipedia.org/wiki/Virtual_method_table\">**virtual function table**</a> (also known as **virtual method table**, **vtable**, **dispatch table**). The necessary table lookup constitutes an indirection and increases the costs for the function invokation.\n\nIf your function is short (i.e. does not perform a lot of work) then this extra overhead can be critically significant. If you function is long is will be negligible.","metadata":{},"id":"370a4417"},{"cell_type":"markdown","source":"### Overriding\n#### Pitfalls","metadata":{},"id":"e27f36b9"},{"cell_type":"markdown","source":"In our example all child classes (`student` and `researcher`) have implemented their own version of `all_info()` and, thus, **overridden** the one from the base class. This is, however, not required. If the base class method is not overridden in a child, the base class method will simply be used.","metadata":{},"id":"960b3f74"},{"cell_type":"code","source":"class staff : public person{\n using person::person;\n};","metadata":{"trusted":true},"execution_count":34,"outputs":[],"id":"99352379"},{"cell_type":"code","source":"person* secretary = new staff( \"Linda\" );\nsecretary->all_info();","metadata":{"trusted":true},"execution_count":35,"outputs":[{"name":"stdout","text":"[person] My name is Linda\n","output_type":"stream"}],"id":"454ccaaf"},{"cell_type":"markdown","source":"Especially in larger code bases it can be come tedious to not lose the overview on which member functions are virtual and which are not. Or whether we are overriding the correct version of a method.","metadata":{},"id":"80e7e3b3"},{"cell_type":"code","source":"#include <iostream>\n\nstruct A {\n void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n};\n\n\nstruct B : public A {\n void func1() { std::cout << \"B::func1 is executing\" << std::endl; };\n virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n};","metadata":{"trusted":true},"execution_count":36,"outputs":[],"id":"bd457522"},{"cell_type":"code","source":"A* obj = new B;\nobj->func1();\nobj->func2( 2 );\nobj->func3();","metadata":{"trusted":true},"execution_count":37,"outputs":[{"name":"stdout","text":"A::func1 is executing\nfunc2 from A: a = 2\nA::func3 is executing\n","output_type":"stream"}],"id":"4a0a85ba"},{"cell_type":"markdown","source":"This did not give the expected results as:\n1. `A::func1` is not virtual, so we don't overide.\n1. While `A::func2` is virtual, the interface of `B::func2` is different, so again no override.\n1. Similarly, while `A::func3` is virtual, its signature is different to that of `B::func3` because of the `const`.\n\nA smart compiler might warn us on the latter two issues, if we ask for it. But it will not warn about the first one:","metadata":{},"id":"6414fc0e"},{"cell_type":"code","source":"%%file fail.cpp\n\n#include <iostream>\n\nstruct A {\n void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n};\n\n\nstruct B : public A {\n void func1() { std::cout << \"B::func1 is executing\" << std::endl; };\n virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n};","metadata":{"trusted":true},"execution_count":39,"outputs":[{"name":"stdout","text":"Overwriting fail.cpp\n","output_type":"stream"}],"id":"3c824171"},{"cell_type":"code","source":"!clang++ -Wall -Wextra -c fail.cpp","metadata":{"trusted":true},"execution_count":40,"outputs":[{"name":"stdout","text":"fail.cpp:13:18: warning: 'B::func2' hides overloaded virtual function [-Woverloaded-virtual]\n virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n ^\nfail.cpp:6:18: note: hidden overloaded virtual function 'A::func2' declared here: type mismatch at 1st parameter ('double' vs 'int')\n virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n ^\nfail.cpp:14:10: warning: 'B::func3' hides overloaded virtual function [-Woverloaded-virtual]\n void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n ^\nfail.cpp:7:18: note: hidden overloaded virtual function 'A::func3' declared here: different qualifiers (const vs none)\n virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n ^\n2 warnings generated.\n","output_type":"stream"}],"id":"29c729c5"},{"cell_type":"markdown","source":"#### Specifier: override\nHowever, we can use the **override** specifier to indicate that we are (intending :) to override a virtual method of the base class. Then the compiler will generate an error, if this fails:","metadata":{},"id":"46b31bab-6112-45c8-8bf3-5137f08169a4"},{"cell_type":"code","source":"%%file fail.cpp\n\n#include <iostream>\n\nstruct A {\n void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n};\n\n\nstruct B : public A {\n void func1() override { std::cout << \"B::func1 is executing\" << std::endl; };\n virtual void func2( int a ) override { std::cout << \"func2 from B: a = \" << a << std::endl; };\n void func3() override { std::cout << \"B::func3 is executing\" << std::endl; }; \n};","metadata":{"trusted":true},"execution_count":41,"outputs":[{"name":"stdout","text":"Overwriting fail.cpp\n","output_type":"stream"}],"id":"8ab90c4e-a3a3-4d26-8be1-6783e75d8bdb"},{"cell_type":"code","source":"!g++ fail.cpp","metadata":{"trusted":true},"execution_count":42,"outputs":[{"name":"stdout","text":"fail.cpp:12:10: error: ‘void B::func1()’ marked ‘override’, but does not override\n void func1() override { std::cout << \"B::func1 is executing\" << std::endl; };\n ^~~~~\nfail.cpp:13:18: error: ‘virtual void B::func2(int)’ marked ‘override’, but does not override\n virtual void func2( int a ) override { std::cout << \"func2 from B: a = \" << a << std::endl; };\n ^~~~~\nfail.cpp:14:10: error: ‘void B::func3()’ marked ‘override’, but does not override\n void func3() override { std::cout << \"B::func3 is executing\" << std::endl; };\n ^~~~~\n","output_type":"stream"}],"id":"f4c9ca56-443e-477c-a1ee-869de3d44341"},{"cell_type":"markdown","source":"#### Speficier: final\nThe `final` specifier allows is to express that this is the final implementation of a virtual method. Further child classes are then not allowed to override it anymore.","metadata":{},"id":"cd972c82-a6c3-4d04-95ec-2880d78f4e43"},{"cell_type":"code","source":"%%file final.cpp\n\n#include <iostream>\n\nclass GrandMother {\npublic:\n virtual void doIt( int a ) = 0;\n void func() {};\n};\n\nclass Mother : public GrandMother {\npublic:\n void doIt( int a ) override // final // (1) creates an error in Daughter\n {\n std::cout << a << \" + 3 = \" << a+3 << std::endl;\n }\n // void func() final {}; // (2) will fail as GrandMother::func is not virtual\n};\n\nclass Daughter : public Mother {\npublic:\n // void doIt( int a ) override { // (3)\n // void doIt( int a ) final {\n void doIt( int a ) override final {\n // void doIt( int a ) {\n std::cout << a << \" * 3 = \" << a*3 << std::endl;\n }\n};\n\nint main() {\n Mother mom;\n mom.doIt( 1 ); \n Daughter sally;\n sally.doIt( 2 );\n}","metadata":{"trusted":true},"execution_count":46,"outputs":[{"name":"stdout","text":"Overwriting final.cpp\n","output_type":"stream"}],"id":"6c7d3d27-6622-497c-a621-ec8a207ae1ae"},{"cell_type":"code","source":"!g++ final.cpp","metadata":{"trusted":true},"execution_count":47,"outputs":[],"id":"55d0dcc1-2f25-4b27-812c-46b1009c6881"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":48,"outputs":[{"name":"stdout","text":"1 + 3 = 4\n2 * 3 = 6\n","output_type":"stream"}],"id":"ae0745fb-02d6-4a15-b59d-08720bdcb1cc"},{"cell_type":"markdown","source":"**Note** that both speficiers `override` and `final` are **optional**. Thus, it is formally not required e.g. to combine `final` with `override`.","metadata":{},"id":"8f3dd806-ae3a-47d9-8380-32b940567b24"},{"cell_type":"markdown","source":"## Pure Virtual Functions and Abstract Classes","metadata":{},"id":"413b3e71"},{"cell_type":"markdown","source":"Consider the following application scenario:\n\n- We want to implement four specialised matrix classes\n - `DenseMatrix`\n - `DenseSymMatrix`\n - `SparseMatrix`\n - `SparseSymMatrix`\n- They all should be children of a common `BaseMatrix` base class.\n- All of them should provide a method `multWithVector()` with the same signature.\n- We want to be able to invoke that method on a base class pointer or reference.\n\n This gives rise to the following questions:\n 1. How should `BaseMatrix::multWithVector()` be implemented?\n 1. Can we enforce that someone who adds another child, say `DenseSkewMatrix` does not forget to implement `DenseSkewMatrix::multWithVector()`?\n\nA sensible implementation of `BaseMatrix::multWithVector()` will not be possible, as the base class has no knowledge on the details of how its children store their matrices. We could only do an empty implementation or one that e.g. throws an exception. The latter would at least lead to a runtime crash, if we call `multWithVector()` on a base class pointer or reference that targets a `DenseSkewMatrix` and the latter did forget to override.\n\nThe preferred solution, however, would be to make `BaseMatrix::multWithVector()` a **pure virtual function**.","metadata":{},"id":"ebb14ead"},{"cell_type":"code","source":"class Vector;\n\nclass BaseMatrix {\n virtual void multWithVector( const Vector& src, Vector& dst ) const = 0;\n}","metadata":{},"execution_count":13,"outputs":[],"id":"3069e8e2"},{"cell_type":"markdown","source":"* A pure virtual function has no implementation in the class where it is declared. It only describes an **interface**.\n* Adding a pure virtual function to a class makes it an **abstract class**, i.e. we cannot instantiate an object of that class.\n* The same holds for its children (via inheritance), as long as they do not implement the method.","metadata":{},"id":"ec47a18b"},{"cell_type":"code","source":"%%file pure.cpp\n\n#include <iostream>\n\nclass Interface {\n\n virtual void setValue( double v ) = 0;\n virtual double getValue() = 0;\n\n};\n\nclass VariantA : public Interface {\nprivate:\n double val_;\npublic:\n virtual void setValue( double v ) override final { val_ = v; };\n};\n\nclass VariantB : public Interface {\nprivate:\n double val_;\npublic:\n virtual void setValue( double v ) override final { val_ = v; };\n double getValue() { return val_; };\n};\n\nint main( void ) {\n // Interface uninstantiable;\n // VariantA obj1;\n VariantB obj2;\n}","metadata":{"trusted":true},"execution_count":56,"outputs":[{"name":"stdout","text":"Overwriting pure.cpp\n","output_type":"stream"}],"id":"24c9d364"},{"cell_type":"code","source":"!g++ pure.cpp","metadata":{"trusted":true},"execution_count":57,"outputs":[],"id":"a6159a96-102e-478f-8e06-68e51e77e1d0"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"400a8a90-8f2a-468c-a9ba-4f124029858e"}]} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1a6233be", + "metadata": {}, + "source": [ + "# Polymorphic or Virtual Classes" + ] + }, + { + "cell_type": "markdown", + "id": "b6f1b161-e13c-4f2f-bb7c-9b2ecbdb8c1a", + "metadata": {}, + "source": [ + "## Virtual Functions" + ] + }, + { + "cell_type": "markdown", + "id": "7ec80016", + "metadata": {}, + "source": [ + "We return to our example from <a href=\"https://www.hanser-kundencenter.de/fachbuch/artikel/9783446458468\">[Go19]</a> and extend it a little." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7942848b", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "class person {\n", + "\n", + "public:\n", + " person( const std::string& name ) : name(name) {};\n", + "\n", + " void all_info() const {\n", + " std::cout << \"[person] My name is \" << name << std::endl;\n", + " }\n", + "\n", + "protected:\n", + " std::string name;\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "f40024e2", + "metadata": {}, + "outputs": [], + "source": [ + "class student : public person {\n", + "\n", + "public:\n", + " student( const std::string& name, const std::string program ) :\n", + " person(name), program(program) {}\n", + "\n", + " void all_info() const {\n", + " std::cout << \"[student] My name is \" << name\n", + " << \", I study \" << program << std::endl;\n", + " }\n", + "\n", + "private:\n", + " std::string program;\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "459a1ce0", + "metadata": {}, + "outputs": [], + "source": [ + "class researcher : public person {\n", + "\n", + "public:\n", + " researcher( const std::string& name, const std::string& field ) :\n", + " person(name), field(field) {}\n", + "\n", + " void all_info() const {\n", + " std::cout << \"[researcher] My name is \" << name\n", + " << \", I work on problems in \" << field << std::endl;\n", + " }\n", + "\n", + "private:\n", + " std::string field;\n", + "};" + ] + }, + { + "cell_type": "markdown", + "id": "c5c9fb7a", + "metadata": {}, + "source": [ + "As `student`s and `researcher`s are `person`s we can e.g. generate a vector of person pointers, where each pointer targets a different university member." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "5ff4684f", + "metadata": {}, + "outputs": [], + "source": [ + "#include <vector>\n", + "\n", + "student joe( \"Joe\", \"Geophysics\" );\n", + "student jane( \"Jane\", \"Geology\" );\n", + "\n", + "researcher erika( \"Erika\", \"Seismology\" );\n", + "person franz( \"Franz\" );\n", + "\n", + "std::vector<person*> uniMembers;\n", + "\n", + "uniMembers.push_back( &joe );\n", + "uniMembers.push_back( &jane );\n", + "uniMembers.push_back( &erika );\n", + "uniMembers.push_back( &franz );" + ] + }, + { + "cell_type": "markdown", + "id": "56cad921", + "metadata": {}, + "source": [ + "* Because of inheritance, we can now have all pointers to our different people in the same vector, although the targeted objects are of different type.\n", + "* Problem is, when we work with the vector entries and ask for info, we only get the minimal stuff from the base class ..." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "aebeaf35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[person] My name is Joe\n", + "[person] My name is Jane\n", + "[person] My name is Erika\n", + "[person] My name is Franz\n" + ] + } + ], + "source": [ + "for( auto memb: uniMembers ) memb->all_info();" + ] + }, + { + "cell_type": "markdown", + "id": "3f7ae736", + "metadata": {}, + "source": [ + "... instead of the full records, such as" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "445c4d28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[student] My name is Jane, I study Geology\n", + "[researcher] My name is Erika, I work on problems in Seismology\n" + ] + } + ], + "source": [ + "jane.all_info();\n", + "erika.all_info();" + ] + }, + { + "cell_type": "markdown", + "id": "977efc72", + "metadata": {}, + "source": [ + "The solution to this is to make our classes **polymorphic**.\n", + "\n", + "***\n", + "### Definition \n", + "A class that contains at least one **virtual function** is called **polymorphic** or **virtual**.\n", + "\n", + "***" + ] + }, + { + "cell_type": "markdown", + "id": "c0f70535-8833-42c4-b1ea-fd5dcdca72f2", + "metadata": {}, + "source": [ + "Let us change our example by making `all_info()` a virtual function." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4b596bd2", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "class person {\n", + "\n", + "public:\n", + " person( const std::string& name ) : name(name) {};\n", + "\n", + " virtual void all_info() const { // <- one small change only ;-)\n", + " std::cout << \"[person] My name is \" << name << std::endl;\n", + " }\n", + "\n", + "protected:\n", + " std::string name;\n", + "};\n", + "\n", + "class student : public person {\n", + "\n", + "public:\n", + " student( const std::string& name, const std::string program ) :\n", + " person(name), program(program) {}\n", + "\n", + " void all_info() const {\n", + " std::cout << \"[student] My name is \" << name\n", + " << \", I study \" << program << std::endl;\n", + " }\n", + "\n", + "private:\n", + " std::string program;\n", + "};\n", + "\n", + "class researcher : public person {\n", + "\n", + "public:\n", + " researcher( const std::string& name, const std::string& field ) :\n", + " person(name), field(field) {}\n", + "\n", + " void all_info() const {\n", + " std::cout << \"[researcher] My name is \" << name\n", + " << \", I work on problems in \" << field << std::endl;\n", + " }\n", + "\n", + "private:\n", + " std::string field;\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "12bebbf9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[student] My name is Joe, I study Geophysics\n", + "[student] My name is Jane, I study Geology\n", + "[researcher] My name is Erika, I work on problems in Seismology\n", + "[person] My name is Franz\n" + ] + } + ], + "source": [ + "#include <vector>\n", + "\n", + "student joe( \"Joe\", \"Geophysics\" );\n", + "student jane( \"Jane\", \"Geology\" );\n", + "\n", + "researcher erika( \"Erika\", \"Seismology\" );\n", + "person franz( \"Franz\" );\n", + "\n", + "std::vector<person*> uniMembers;\n", + "\n", + "uniMembers.push_back( &joe );\n", + "uniMembers.push_back( &jane );\n", + "uniMembers.push_back( &erika );\n", + "uniMembers.push_back( &franz );\n", + "\n", + "for( auto memb: uniMembers ) memb->all_info();" + ] + }, + { + "cell_type": "markdown", + "id": "ef04a619", + "metadata": {}, + "source": [ + "#### Explanation \n", + "So what is the difference here? Whenever we work with a **pointer** `pVar` or a **reference** `rVar` to a class and invoke a function `myFunc()` via that on the target the compiler will check the following:\n", + "\n", + "1. What is the **static type** of `pVal` and or `pVar`, i.e. how were they declared?\n", + "1. Does that class have a method `myFunc()`?\n", + "1. Is `myFunc()` accessible, i.e. public, or not, i.e. private?\n", + "1. Is it a **virtual function**?\n", + " * No: Then invoke it.\n", + " * Yes: Check what the **dynamic type** is of `pVal` or `pVar`, i.e. the type of the object they target.\n", + " Then invoke `myFunc()` for that type." + ] + }, + { + "cell_type": "markdown", + "id": "ae46b4e3", + "metadata": {}, + "source": [ + "#### Remark 1 \n", + "This only works for pointers and references, but not for variables, even if they are initialised or copy constructed from a child object." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f371e175-cbc1-45e4-b8bb-06b8ebc92ef1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[person] My name is Joe\n" + ] + } + ], + "source": [ + "person alias{joe};\n", + "alias.all_info();" + ] + }, + { + "cell_type": "markdown", + "id": "2d6da352", + "metadata": {}, + "source": [ + "Only the parts of `joe` that belong to its `person` base class are used for the initialisation of `alias`. This is sometimes refered to as **slicing**." + ] + }, + { + "cell_type": "markdown", + "id": "ad22ac97", + "metadata": {}, + "source": [ + "#### Remark 2 \n", + "The decision which version of `all_info()` must be invoked **cannot** be made by the compiler, as it may not have the necessary information on the type of the target of a pointer or reference. It can only be made at **run-time**." + ] + }, + { + "cell_type": "markdown", + "id": "bcb61161", + "metadata": {}, + "source": [ + "### Terminology\n", + "Since the decision which virtual method is invoked on a pointer or reference to a polymorphic object is performed at run-time, one calls this **dynamic dispatch** (also often refered to as late or dynamic binding in C++, but whether this is correct is disputed).\n", + "\n", + "It constitutes a form of **dynamic polymorphism**, as opposed to the static polymorphism of overloading or templates, which get's resolved at compile-time." + ] + }, + { + "cell_type": "markdown", + "id": "370a4417", + "metadata": {}, + "source": [ + "### Costs\n", + "In order to be able to correctly dispatch the virtual function call at runtime, the compiler generates a <a href=\"https://en.wikipedia.org/wiki/Virtual_method_table\">**virtual function table**</a> (also known as **virtual method table**, **vtable**, **dispatch table**). The necessary table lookup constitutes an indirection and increases the costs for the function invokation.\n", + "\n", + "If your function is short (i.e. does not perform a lot of work) then this extra overhead can be critically significant. If you function is long is will be negligible." + ] + }, + { + "cell_type": "markdown", + "id": "e27f36b9", + "metadata": {}, + "source": [ + "### Overriding\n", + "#### Pitfalls" + ] + }, + { + "cell_type": "markdown", + "id": "960b3f74", + "metadata": {}, + "source": [ + "In our example all child classes (`student` and `researcher`) have implemented their own version of `all_info()` and, thus, **overridden** the one from the base class. This is, however, not required. If the base class method is not overridden in a child, the base class method will simply be used." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "99352379", + "metadata": {}, + "outputs": [], + "source": [ + "class staff : public person{\n", + " using person::person;\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "454ccaaf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[person] My name is Linda\n" + ] + } + ], + "source": [ + "person* secretary = new staff( \"Linda\" );\n", + "secretary->all_info();" + ] + }, + { + "cell_type": "markdown", + "id": "80e7e3b3", + "metadata": {}, + "source": [ + "Especially in larger code bases it can be come tedious to not lose the overview on which member functions are virtual and which are not. Or whether we are overriding the correct version of a method." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "bd457522", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "struct A {\n", + " void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n", + " virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n", + " virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n", + "};\n", + "\n", + "\n", + "struct B : public A {\n", + " void func1() { std::cout << \"B::func1 is executing\" << std::endl; };\n", + " virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n", + " void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "4a0a85ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A::func1 is executing\n", + "func2 from A: a = 2\n", + "A::func3 is executing\n" + ] + } + ], + "source": [ + "A* obj = new B;\n", + "obj->func1();\n", + "obj->func2( 2 );\n", + "obj->func3();" + ] + }, + { + "cell_type": "markdown", + "id": "6414fc0e", + "metadata": {}, + "source": [ + "This did not give the expected results as:\n", + "1. `A::func1` is not virtual, so we don't overide.\n", + "1. While `A::func2` is virtual, the interface of `B::func2` is different, so again no override.\n", + "1. Similarly, while `A::func3` is virtual, its signature is different to that of `B::func3` because of the `const`.\n", + "\n", + "A smart compiler might warn us on the latter two issues, if we ask for it. But it will not warn about the first one:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "3c824171", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting fail.cpp\n" + ] + } + ], + "source": [ + "%%file fail.cpp\n", + "\n", + "#include <iostream>\n", + "\n", + "struct A {\n", + " void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n", + " virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n", + " virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n", + "};\n", + "\n", + "\n", + "struct B : public A {\n", + " void func1() { std::cout << \"B::func1 is executing\" << std::endl; };\n", + " virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n", + " void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "29c729c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fail.cpp:13:18: warning: 'B::func2' hides overloaded virtual function [-Woverloaded-virtual]\n", + " virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a << std::endl; };\n", + " ^\n", + "fail.cpp:6:18: note: hidden overloaded virtual function 'A::func2' declared here: type mismatch at 1st parameter ('double' vs 'int')\n", + " virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n", + " ^\n", + "fail.cpp:14:10: warning: 'B::func3' hides overloaded virtual function [-Woverloaded-virtual]\n", + " void func3() { std::cout << \"B::func3 is executing\" << std::endl; }; \n", + " ^\n", + "fail.cpp:7:18: note: hidden overloaded virtual function 'A::func3' declared here: different qualifiers (const vs none)\n", + " virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n", + " ^\n", + "2 warnings generated.\n" + ] + } + ], + "source": [ + "!clang++ -Wall -Wextra -c fail.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "46b31bab-6112-45c8-8bf3-5137f08169a4", + "metadata": {}, + "source": [ + "#### Specifier: override\n", + "However, we can use the **override** specifier to indicate that we are (intending :) to override a virtual method of the base class. Then the compiler will generate an error, if this fails:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "8ab90c4e-a3a3-4d26-8be1-6783e75d8bdb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting fail.cpp\n" + ] + } + ], + "source": [ + "%%file fail.cpp\n", + "\n", + "#include <iostream>\n", + "\n", + "struct A {\n", + " void func1() { std::cout << \"A::func1 is executing\" << std::endl; };\n", + " virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a << std::endl; }; \n", + " virtual void func3() const { std::cout << \"A::func3 is executing\" << std::endl; }; \n", + "};\n", + "\n", + "\n", + "struct B : public A {\n", + " void func1() override { std::cout << \"B::func1 is executing\" << std::endl; };\n", + " virtual void func2( int a ) override { std::cout << \"func2 from B: a = \" << a << std::endl; };\n", + " void func3() override { std::cout << \"B::func3 is executing\" << std::endl; }; \n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f4c9ca56-443e-477c-a1ee-869de3d44341", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fail.cpp:12:10: error: ‘void B::func1()’ marked ‘override’, but does not override\n", + " void func1() override { std::cout << \"B::func1 is executing\" << std::endl; };\n", + " ^~~~~\n", + "fail.cpp:13:18: error: ‘virtual void B::func2(int)’ marked ‘override’, but does not override\n", + " virtual void func2( int a ) override { std::cout << \"func2 from B: a = \" << a << std::endl; };\n", + " ^~~~~\n", + "fail.cpp:14:10: error: ‘void B::func3()’ marked ‘override’, but does not override\n", + " void func3() override { std::cout << \"B::func3 is executing\" << std::endl; };\n", + " ^~~~~\n" + ] + } + ], + "source": [ + "!g++ fail.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "cd972c82-a6c3-4d04-95ec-2880d78f4e43", + "metadata": {}, + "source": [ + "#### Speficier: final\n", + "The `final` specifier allows is to express that this is the final implementation of a virtual method. Further child classes are then not allowed to override it anymore." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "6c7d3d27-6622-497c-a621-ec8a207ae1ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting final.cpp\n" + ] + } + ], + "source": [ + "%%file final.cpp\n", + "\n", + "#include <iostream>\n", + "\n", + "class GrandMother {\n", + "public:\n", + " virtual void doIt( int a ) = 0;\n", + " void func() {};\n", + "};\n", + "\n", + "class Mother : public GrandMother {\n", + "public:\n", + " void doIt( int a ) override // final // (1) creates an error in Daughter\n", + " {\n", + " std::cout << a << \" + 3 = \" << a+3 << std::endl;\n", + " }\n", + " // void func() final {}; // (2) will fail as GrandMother::func is not virtual\n", + "};\n", + "\n", + "class Daughter : public Mother {\n", + "public:\n", + " // void doIt( int a ) override { // (3)\n", + " // void doIt( int a ) final {\n", + " void doIt( int a ) override final {\n", + " // void doIt( int a ) {\n", + " std::cout << a << \" * 3 = \" << a*3 << std::endl;\n", + " }\n", + "};\n", + "\n", + "int main() {\n", + " Mother mom;\n", + " mom.doIt( 1 ); \n", + " Daughter sally;\n", + " sally.doIt( 2 );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "55d0dcc1-2f25-4b27-812c-46b1009c6881", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ final.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "ae0745fb-02d6-4a15-b59d-08720bdcb1cc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 + 3 = 4\n", + "2 * 3 = 6\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "8f3dd806-ae3a-47d9-8380-32b940567b24", + "metadata": {}, + "source": [ + "**Note** that both speficiers `override` and `final` are **optional**. Thus, it is formally not required e.g. to combine `final` with `override`." + ] + }, + { + "cell_type": "markdown", + "id": "413b3e71", + "metadata": {}, + "source": [ + "## Pure Virtual Functions and Abstract Classes" + ] + }, + { + "cell_type": "markdown", + "id": "ebb14ead", + "metadata": {}, + "source": [ + "Consider the following application scenario:\n", + "\n", + "- We want to implement four specialised matrix classes\n", + " - `DenseMatrix`\n", + " - `DenseSymMatrix`\n", + " - `SparseMatrix`\n", + " - `SparseSymMatrix`\n", + "- They all should be children of a common `BaseMatrix` base class.\n", + "- All of them should provide a method `multWithVector()` with the same signature.\n", + "- We want to be able to invoke that method on a base class pointer or reference.\n", + "\n", + " This gives rise to the following questions:\n", + " 1. How should `BaseMatrix::multWithVector()` be implemented?\n", + " 1. Can we enforce that someone who adds another child, say `DenseSkewMatrix` does not forget to implement `DenseSkewMatrix::multWithVector()`?\n", + "\n", + "A sensible implementation of `BaseMatrix::multWithVector()` will not be possible, as the base class has no knowledge on the details of how its children store their matrices. We could only do an empty implementation or one that e.g. throws an exception. The latter would at least lead to a runtime crash, if we call `multWithVector()` on a base class pointer or reference that targets a `DenseSkewMatrix` and the latter did forget to override.\n", + "\n", + "The preferred solution, however, would be to make `BaseMatrix::multWithVector()` a **pure virtual function**." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3069e8e2", + "metadata": {}, + "outputs": [], + "source": [ + "class Vector;\n", + "\n", + "class BaseMatrix {\n", + " virtual void multWithVector( const Vector& src, Vector& dst ) const = 0;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "ec47a18b", + "metadata": {}, + "source": [ + "* A pure virtual function has no implementation in the class where it is declared. It only describes an **interface**.\n", + "* Adding a pure virtual function to a class makes it an **abstract class**, i.e. we cannot instantiate an object of that class.\n", + "* The same holds for its children (via inheritance), as long as they do not implement the method." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "24c9d364", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting pure.cpp\n" + ] + } + ], + "source": [ + "%%file pure.cpp\n", + "\n", + "#include <iostream>\n", + "\n", + "class Interface {\n", + "\n", + " virtual void setValue( double v ) = 0;\n", + " virtual double getValue() = 0;\n", + "\n", + "};\n", + "\n", + "class VariantA : public Interface {\n", + "private:\n", + " double val_;\n", + "public:\n", + " virtual void setValue( double v ) override final { val_ = v; };\n", + "};\n", + "\n", + "class VariantB : public Interface {\n", + "private:\n", + " double val_;\n", + "public:\n", + " virtual void setValue( double v ) override final { val_ = v; };\n", + " double getValue() { return val_; };\n", + "};\n", + "\n", + "int main( void ) {\n", + " // Interface uninstantiable;\n", + " // VariantA obj1;\n", + " VariantB obj2;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "a6159a96-102e-478f-8e06-68e51e77e1d0", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ pure.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "400a8a90-8f2a-468c-a9ba-4f124029858e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "C++14", + "language": "C++14", + "name": "xcpp14" + }, + "language_info": { + "codemirror_mode": "text/x-c++src", + "file_extension": ".cpp", + "mimetype": "text/x-c++src", + "name": "c++", + "version": "14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/11_Template_Basics.ipynb b/notebooks/11_Template_Basics.ipynb index 304f572..714451f 100644 --- a/notebooks/11_Template_Basics.ipynb +++ b/notebooks/11_Template_Basics.ipynb @@ -1 +1 @@ -{"metadata":{"kernelspec":{"name":"xcpp17","display_name":"C++17","language":"C++17"},"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":"markdown","source":"# Template Programming: Basics","metadata":{},"id":"741a2490"},{"cell_type":"markdown","source":"## Motivation\n\nIn our first notebook **01_Overloading** we started with implementing two free-functions that returned the magnitude of their argument:","metadata":{"tags":[]},"id":"a61d218d"},{"cell_type":"code","source":"// Version for int\nint getMagnitude( int input ) {\n return input > 0 ? input : -input;\n}","metadata":{},"execution_count":1,"outputs":[],"id":"d4a7139f"},{"cell_type":"code","source":"// Version for double\ndouble getMagnitude( double input ) {\n return input > 0.0 ? input : -input;\n}","metadata":{},"execution_count":2,"outputs":[],"id":"cfbe1b82"},{"cell_type":"markdown","source":"Comparison of the code shows that there are three differences between the two functions:\n1. type of argument\n1. type of return value\n1. type of literal used\n\nThe last one can be eliminated by performing a static cast.","metadata":{},"id":"6f937e9d"},{"cell_type":"code","source":"// Version for double\ndouble getMagnitude( double input ) {\n return input > static_cast< double >( 0 ) ? input : -input;\n}","metadata":{},"execution_count":3,"outputs":[],"id":"0225265d"},{"cell_type":"markdown","source":"Now imagine that we want to have one function for every signed integer data type and each floating point type in C++, i.e.\n- signed char\n- signed short\n- signed int\n- signed long\n- signed long long\n- float\n- double\n- long double\n\nThis implies that we need to implement eight different versions of `getMagnitude`, which, however, are always following the same structure:\n\n```c++\n// Basic pattern of our free-function for a datatype TYPE is:\nTYPE getMagnitude( TYPE input ) {\n return input > static_cast< TYPE >( 0 ) ? input : -input;\n}\n```","metadata":{},"id":"13e637a9"},{"cell_type":"markdown","source":"---\n**Alternatives?**\n\nIt would be nice, if we did not have to write the (basically) same code multiple times\n- Repetition is dull and error-prone\n- We need to maintain (basically) the same code piece multiple times -> more work and error-prone\n\nFortunately there are ways to avoid this. The two prominent ones applicable in our situation are:\n1. (Automatic) Code Generation\n1. Generic Programming","metadata":{},"id":"3013b562"},{"cell_type":"markdown","source":"### (Automatic) Code Generation\n\nHere the idea is to let the computer generate the source code that the compiler sees itself following our instructions.\n\nFor this we need some (external) code generator. Often this is written in some other language than the generated code itself (e.g. in Python).\n\n> An example of this would be the \n<a href=\"https://fenicsproject.org\">FEniCS</a> project, a popular open-source computing platform for solving partial differential equations with Finite Elements. You describe your PDE problem in Python and from this efficient C++ code is generated, compiled and executed (very rough description :-) \n\nOften such generators are tuned to a specific field of problems and involve a so called\nDSL (domain specific language) to describe what is to be solved/generated.\n\n> For more on this see e.g. the publications of the <a href=\"https://www.exastencils.fau.de\">ExaStencils</a> project.\n\nAutomatic code generation plays an increasingly important role in high-performance scientific applications for **performance tuning** and **(performance) portability** (think CPU vs. GPU vs FPGA vs ...)","metadata":{"tags":[]},"id":"6dc7ea86"},{"cell_type":"markdown","source":"### 'Inline' Code Generation\nIn C++ it is possible to do some (limited) code generation on-the-fly using the capabilities of the **preprocessor**. An interesting overview on that can be found e.g. in <a href=\"https://link.springer.com/book/10.1007/978-3-662-48550-7\">C++ Metaprogrammierung</a>, Lemke, 2016, Springer.\n\nWe are going to take a look at this as an example, before proceeding to templates.","metadata":{},"id":"3191c55f"},{"cell_type":"code","source":"%%file getMag.cpp\n\n// we use a parameterised macro that represents the pattern of our function\n#define GETMAG( TYPE ) \\\n TYPE getMagnitude( TYPE value ) { \\\n return value > static_cast< TYPE >( 0 ) ? value : -value; \\\n}\n\n// Now we can let the preprocessor generate the source code by using the\n// macro with the corresponding datatype we need\nGETMAG( int )\nGETMAG( double )\nGETMAG( float )","metadata":{},"execution_count":4,"outputs":[{"name":"stdout","output_type":"stream","text":"Writing getMag.cpp\n"}],"id":"988d30ea"},{"cell_type":"markdown","source":"Now let us check how the result of running the preprocessor on that source code looks like:","metadata":{},"id":"625b1b11"},{"cell_type":"code","source":"!cpp getMag.cpp","metadata":{},"execution_count":5,"outputs":[{"name":"stdout","output_type":"stream","text":"# 1 \"getMag.cpp\"\n# 1 \"<built-in>\"\n# 1 \"<command-line>\"\n# 1 \"/usr/include/stdc-predef.h\" 1 3 4\n# 1 \"<command-line>\" 2\n# 1 \"getMag.cpp\"\n# 10 \"getMag.cpp\"\nint getMagnitude( int value ) { return value > static_cast< int >( 0 ) ? value : -value; }\ndouble getMagnitude( double value ) { return value > static_cast< double >( 0 ) ? value : -value; }\nfloat getMagnitude( float value ) { return value > static_cast< float >( 0 ) ? value : -value; }\n"}],"id":"8baeee45"},{"cell_type":"markdown","source":"---\nPreprocessing is an integral part of compilation of a C++ program (think of the `#include` directive) so this integrates seamlessly:","metadata":{},"id":"9ea5d77e"},{"cell_type":"code","source":"%%file getMag.cpp\n\n// we use a parameterised macro that represents the pattern of our function\n#define GETMAG( TYPE ) \\\n TYPE getMagnitude( TYPE value ) { \\\n return value < static_cast< TYPE >( 0 ) ? -value : value; \\\n}\n\n// Now we can let the preprocessor generate the source code by using the\n// macro with the corresponding datatype we need\nGETMAG( signed char )\nGETMAG( short )\nGETMAG( int )\nGETMAG( long )\nGETMAG( float )\nGETMAG( double )\n \n#include <iostream>\nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n std::cout << \"|\" << s << \"| = \" << getMagnitude( s ) << std::endl;\n std::cout << '|' << x << \"| = \" << getMagnitude( x ) << std::endl;\n std::cout << '|' << v << \"| = \" << getMagnitude( v ) << std::endl;\n}","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"Overwriting getMag.cpp\n"}],"id":"b1d7de95"},{"cell_type":"code","source":"!g++ -Wall -Wextra getMag.cpp","metadata":{},"execution_count":7,"outputs":[],"id":"b85f934b"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","output_type":"stream","text":"|-2| = 2\n|3.5| = 3.5\n|12.34| = 12.34\n"}],"id":"00684f47"},{"cell_type":"markdown","source":"The downside of this approach is that the C/C++ preprocessor has nearly no understanding of the language itself. Hence, it cannot perform any syntax checking or the like.\n\nWriting longer functions will become irksome, also, due to the line continuation stuff. *(and no syntax high-lighting or indentation ... by editors).*\n\nAlso we will need to **explicitely have a line for each `getMagnitude()` version** that might be used. Code will not be generated on a need-to-have basis.","metadata":{},"id":"6631c752"},{"cell_type":"markdown","source":"### Generic Programming\nThe idea here is to not use an external code generator, but instead provide functionality in the programming language itself that allows to accomplish what we want.\n\nFrom <a href=\"https://en.wikipedia.org/wiki/Generic_programming\">Wikipedia</a>:\n> Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. \n\nIn C++ the approach to support generic programming is to use **templates**.","metadata":{},"id":"dda3bd44"},{"cell_type":"markdown","source":"## C++ Template Basics\n\nSo how would we takle our problem with templates?\n\nInstead of multiple free-functions we implement a **single templated version** of it:","metadata":{},"id":"c9b5ee37"},{"cell_type":"code","source":"template< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"90ddfa90"},{"cell_type":"markdown","source":"When the compiler recognises that an instance of the templated function is needed for a specific data-type it will instantiate it (i.e. it will compile machine-code for this version): ","metadata":{},"id":"e2df86d3"},{"cell_type":"code","source":"#include <iostream>\nshort s = -5;\nstd::cout << \"|\" << s << \"| = \" << getMagnitude( s );","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"|-5| = 5","output_type":"stream"}],"id":"f5be2836"},{"cell_type":"markdown","source":"---\nA closer look at the **function template**:\n\n```c++\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value < static_cast< T >( 0 ) ? -value : value;\n}\n```\n\n* `template< typename T >`\n - informs the compiler that it is a templated function\n - in the example there is a single **template parameter** (often denoted as `T`, but that is\n just convention)\n - `typename` specifies `T` to be a **type** template parameter (`class` can be used interchangeably)\n - instead of a datatype a parameter can also be a basic datatype ( e.g. `template <int n>` ), or a *template template parameter*\n* In the body of the template declaration `T` is a typedef-name that aliases the type supplied when the template is instantiated","metadata":{},"id":"740ee0bd"},{"cell_type":"markdown","source":"---\nNow let's take a look at the **instantiation**:","metadata":{},"id":"d6754844-ef2d-496e-adab-1367a463c19f"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n \nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n getMagnitude( s );\n getMagnitude( x );\n getMagnitude( v );\n getMagnitude( -10L );\n}","metadata":{"trusted":true},"execution_count":36,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"8f2ab60c"},{"cell_type":"code","source":"!g++ -save-temps -c withTemplates.cpp","metadata":{"trusted":true},"execution_count":38,"outputs":[],"id":"96551727-c1b0-4b97-9bff-11e9eeac731e"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":39,"outputs":[{"name":"stdout","text":"0000000000000000 T main\n0000000000000000 W double getMagnitude<double>(double)\n0000000000000000 W float getMagnitude<float>(float)\n0000000000000000 W int getMagnitude<int>(int)\n0000000000000000 W long getMagnitude<long>(long)\n","output_type":"stream"}],"id":"d3e0f1fc-f85a-4adc-a63f-ba7fdea02a2d"},{"cell_type":"markdown","source":"We observe that the object file contains instantiations of all four versions that are required in our source code, *but not more*. This is called **implicit instantiation**.\n\nA look at the file after preprocessing (`-save-temps` option) shows that no extra source code got generated. It really is something the compiler handles for us internally.","metadata":{},"id":"d63382c0-e766-422b-ba97-7824de507c3a"},{"cell_type":"code","source":"!cat withTemplates.ii","metadata":{},"execution_count":19,"outputs":[{"name":"stdout","output_type":"stream","text":"# 1 \"withTemplates.cpp\"\n# 1 \"<built-in>\"\n# 1 \"<command-line>\"\n# 1 \"/usr/include/stdc-predef.h\" 1 3 4\n# 1 \"<command-line>\" 2\n# 1 \"withTemplates.cpp\"\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n getMagnitude( s );\n getMagnitude( x );\n getMagnitude( v );\n getMagnitude( -10L );\n}\n"}],"id":"9c620720-b4cc-46a0-bde0-57452bcd3a0b"},{"cell_type":"markdown","source":"We can, however, also **explicitely** request the compiler to instantiate a version for a specific template argument:","metadata":{},"id":"5cfc8599"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\n// explicit instantiation\ntemplate float getMagnitude( float );\n\nint main() {\n\n // implicit instantiation\n getMagnitude( -2 );\n}","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"38aa85e7-83d9-4ae0-ac05-80cf187ee9f3"},{"cell_type":"code","source":"!g++ -c withTemplates.cpp","metadata":{"trusted":true},"execution_count":10,"outputs":[],"id":"76bfe507-bc01-457a-aeb5-38e2db572cac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"0000000000000000 T main\n0000000000000000 W float getMagnitude<float>(float)\n0000000000000000 W int getMagnitude<int>(int)\n","output_type":"stream"}],"id":"e707d5e2-6d8d-4ee9-9046-b749d154146b"},{"cell_type":"markdown","source":"When might explicit instantiation be required?\n\nLet us modify our example to have the usual structure of *header file* (.hpp) and *source code file* (.cpp) and a separate *driver file*:","metadata":{},"id":"76ecc704-ac09-4343-b561-7817373aa1c9"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}","metadata":{"trusted":true},"execution_count":12,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"a854c3ce-1865-4ec1-b092-87be2a50d13f"},{"cell_type":"code","source":"%%file withTemplates.hpp\n\n// only a prototype\ntemplate< typename T > T getMagnitude( T value );","metadata":{"trusted":true},"execution_count":13,"outputs":[{"name":"stdout","text":"Writing withTemplates.hpp\n","output_type":"stream"}],"id":"b3b433db-8509-43fb-9946-579ffd4c5dfb"},{"cell_type":"code","source":"%%file driver.cpp\n\n#include \"withTemplates.hpp\"\nint main() {\n getMagnitude( -1.2345f );\n}","metadata":{"trusted":true},"execution_count":29,"outputs":[{"name":"stdout","text":"Overwriting driver.cpp\n","output_type":"stream"}],"id":"c96bbc17-3771-453b-9544-eae96dd936f0"},{"cell_type":"code","source":"!g++ driver.cpp","metadata":{"trusted":true},"execution_count":30,"outputs":[{"name":"stdout","text":"/tmp/ccbAB37j.o: In function `main':\ndriver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\ncollect2: error: ld returned 1 exit status\n","output_type":"stream"}],"id":"0cea5aa2-240b-493c-8afc-c560b0dff82c"},{"cell_type":"markdown","source":"Okay, that is not different from the usual. The source code of `getMagnitude()` is inside `withTemplates.cpp`. So let us also compile this source code file:","metadata":{},"id":"976e6ef4-ce58-4a4f-acf9-bff9d122923d"},{"cell_type":"code","source":"!g++ driver.cpp withTemplates.cpp","metadata":{"trusted":true},"execution_count":16,"outputs":[{"name":"stdout","text":"/tmp/ccNQjiNk.o: In function `main':\ndriver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\ncollect2: error: ld returned 1 exit status\n","output_type":"stream"}],"id":"e98425fb-dc85-4e89-9f9b-dc310b1c65bb"},{"cell_type":"markdown","source":"Still a problem! Why's that?","metadata":{},"id":"0ace12fa-d5a8-48ab-8d9b-250c71847812"},{"cell_type":"markdown","source":"---\n\n\n*intentionally left blank ;-)*\n\n\n---","metadata":{},"id":"bf20a5c9-5d48-405f-861e-afc7d699ff41"},{"cell_type":"markdown","source":"When we compile the driver the compiler will recognise that it need a version of `getMagnitude()` for `float`:","metadata":{},"id":"1517f452-ef0e-471a-82c0-880e829a51ff"},{"cell_type":"code","source":"!g++ -c driver.cpp","metadata":{"trusted":true},"execution_count":17,"outputs":[],"id":"846d3074-53c4-4dfe-94af-43b82f49d0a8"},{"cell_type":"code","source":"!nm -C driver.o","metadata":{"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":" U _GLOBAL_OFFSET_TABLE_\n0000000000000000 T main\n U float getMagnitude<float>(float)\n","output_type":"stream"}],"id":"7f84b616-97d9-4ce0-a588-7bed26c4b4aa"},{"cell_type":"markdown","source":"However, it cannot instantiate it, because it does not see the definition of the template function, but only its prototype!\n\n- That is no error.\n- Finding an instance is then delegated to the linker. (Just as in a non-template case)\n\nOkay. So now let us compile the source code file and check the contents of the resulting object file:","metadata":{},"id":"43c992a6-7dca-4aab-8293-328b0cd8bf76"},{"cell_type":"code","source":"!g++ -c withTemplates.cpp","metadata":{"trusted":true},"execution_count":19,"outputs":[],"id":"15c7f98e-33fd-4bca-8293-5a19352c43ac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":20,"outputs":[],"id":"5f6a4521-affc-4147-a33a-a7fd4f6051ce"},{"cell_type":"markdown","source":"Oops. Nothing. Why?\n\nBecause in the source code file no template instance is required and the template function itself cannot be compiled without an argument for the datatype!","metadata":{},"id":"b955ee26-e340-44e5-a264-66beda062321"},{"cell_type":"markdown","source":"---\nSplitting declaration and definition of a template function requires us to use explicit instantiation.","metadata":{},"id":"6bd45ab9-c3b9-4ec2-b093-1a2f327a0526"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\ntemplate float getMagnitude( float );","metadata":{"trusted":true},"execution_count":22,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"e0aa1181-d4b2-4fc1-95fe-ead0f86b2d5b"},{"cell_type":"code","source":"!g++ -save-temps driver.cpp withTemplates.cpp","metadata":{"trusted":true},"execution_count":25,"outputs":[],"id":"e86d0de4-3e7e-48d1-8b2f-3a2b29e44842"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":26,"outputs":[{"name":"stdout","text":"0000000000000000 W float getMagnitude<float>(float)\n","output_type":"stream"}],"id":"380b4117-4a81-45dc-a578-88f233d8726e"},{"cell_type":"markdown","source":"### Template Classes\n\nNaturally in C++ free-functions are not the only aspect we can templatise. We can also do so for member functions of classes and complete classes. The latter we have already seen e.g. with the STL containers.","metadata":{},"id":"9368884e"},{"cell_type":"markdown","source":"As an example let us write a small wrapper class for a static array (note that the useful variant of this would be `std::array` from the STL).","metadata":{},"id":"f91b42be"},{"cell_type":"code","source":"template< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};","metadata":{"trusted":true},"execution_count":33,"outputs":[],"id":"5d7941c5"},{"cell_type":"code","source":"StaticArray< double, 3 > shortVec;\nshortVec.getSize()","metadata":{"trusted":true},"execution_count":34,"outputs":[{"execution_count":34,"output_type":"execute_result","data":{"text/plain":"3"},"metadata":{}}],"id":"69a7c099"},{"cell_type":"markdown","source":"Observations:\n1. The syntax for the template class definition is similar to that of template functions. We have the normal class part with a `template< 'argument list' >` in front of it.\n1. Template parameters are not restricted to type parameters. We can also use **non-type parameters**:\n - integer types (`int`, `short`, `bool`, ...)\n - enum value of an enumeration types\n - floating-point types (since C++20)\n - pointers and references \nthe argument for instantiation then is a value of that type.\n1. We do not need to store the size `N` of the array in the class as an attribute. Instead we can use the template parameter which will be replaced by the argument value provided at instantiation.\n1. Contrary to our previous examples we need to specifiy the template arguments explicitely for the instantiation to work. The compiler cannot determine them from our use of the default constructor. So **CTAD (Class Template Argument Deduction)** will not work.","metadata":{},"id":"db743abf"},{"cell_type":"code","source":"StaticArray longVec;","metadata":{"trusted":true},"execution_count":40,"outputs":[{"name":"stderr","text":"input_line_17:2:2: error: use of class template 'StaticArray' requires template arguments\n StaticArray longVec;\n ^\ninput_line_12:2:8: note: template is declared here\nstruct StaticArray {\n ^\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"a2978a5c"},{"cell_type":"code","source":"// In C++17 the following example of CTAD works; compiler deduces type\n// of vector entries from that of the initialiser list.\n#include <vector>\nstd::vector v{1,2,3};\nstd::vector x{0.1,2.0};","metadata":{"trusted":true},"execution_count":2,"outputs":[],"id":"acbd3d96-da66-4fb6-9732-66508a2544fe"},{"cell_type":"markdown","source":"### Lazy vs Eager Instantiation","metadata":{},"id":"a8f52ca7"},{"cell_type":"markdown","source":"In C++ **implicit instantiation is lazy**. In the case of a template class this means that member functions that are not called do not get instantiated! \n\n**Explicit instantiation**, on the other hand, is eager.\n\nWe can see this from the following (slightly extended) example taken from \nRainer Grimm's blog <a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++ in der Praxis</a>.","metadata":{},"id":"9f21c5d4"},{"cell_type":"code","source":"%%file lazy.cpp\n\n#include <cmath>\n#include <string>\n\ntemplate< typename T >\nstruct Number {\n int absValue() {\n return std::abs( val );\n }\n T val{};\n};\n\n// force explicit instantiation of struct (this is eager!) (A)\n// template struct Number< std::string >;\n\nint main() {\n\n // results in implicit instantiation (this is lazy!)\n Number< std::string > num;\n\n // results in instantiation of member function (B)\n // num.absValue();\n}","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"Overwriting lazy.cpp\n","output_type":"stream"}],"id":"a06c3b23"},{"cell_type":"code","source":"!g++ lazy.cpp","metadata":{"trusted":true},"execution_count":8,"outputs":[{"name":"stdout","text":"lazy.cpp: In instantiation of ‘int Number<T>::absValue() [with T = std::__cxx11::basic_string<char>]’:\nlazy.cpp:14:17: required from here\nlazy.cpp:8:20: error: no matching function for call to ‘abs(std::__cxx11::basic_string<char>&)’\n return std::abs( val );\n ~~~~~~~~^~~~~~~\nIn file included from /usr/include/c++/7/cmath:47:0,\n from lazy.cpp:2:\n/usr/include/c++/7/bits/std_abs.h:102:3: note: candidate: constexpr __float128 std::abs(__float128)\n abs(__float128 __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:102:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘__float128’\n/usr/include/c++/7/bits/std_abs.h:84:3: note: candidate: constexpr __int128 std::abs(__int128)\n abs(__GLIBCXX_TYPE_INT_N_0 __x) { return __x >= 0 ? __x : -__x; }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:84:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘__int128’\n/usr/include/c++/7/bits/std_abs.h:78:3: note: candidate: constexpr long double std::abs(long double)\n abs(long double __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:78:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long double’\n/usr/include/c++/7/bits/std_abs.h:74:3: note: candidate: constexpr float std::abs(float)\n abs(float __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:74:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘float’\n/usr/include/c++/7/bits/std_abs.h:70:3: note: candidate: constexpr double std::abs(double)\n abs(double __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:70:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘double’\n/usr/include/c++/7/bits/std_abs.h:61:3: note: candidate: long long int std::abs(long long int)\n abs(long long __x) { return __builtin_llabs (__x); }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:61:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long long int’\n/usr/include/c++/7/bits/std_abs.h:56:3: note: candidate: long int std::abs(long int)\n abs(long __i) { return __builtin_labs(__i); }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:56:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long int’\nIn file included from /usr/include/c++/7/bits/std_abs.h:38:0,\n from /usr/include/c++/7/cmath:47,\n from lazy.cpp:2:\n/usr/include/stdlib.h:837:12: note: candidate: int abs(int)\n extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;\n ^~~\n/usr/include/stdlib.h:837:12: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘int’\n","output_type":"stream"}],"id":"06c4ffb9"},{"cell_type":"markdown","source":"Let us check this for our previous example, too.","metadata":{},"id":"c89daa16"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};\n\nint main() {\n\n StaticArray< float, 5 > shortVec;\n StaticArray< short, 100 > longVec;\n StaticArray< float, 5 > shortVec2;\n std::cout << longVec.getSize();\n}","metadata":{},"execution_count":2,"outputs":[],"id":"05d338d8"},{"cell_type":"markdown","source":"Questions:\n- Will `getSize()` be instantiated for `StaticArray< float, 5 >`?\n- How many times will `StaticArray< float, 5 >` be instantiated?\n\nIn order to answer these let us feed the code to the source-to-source translator\n<a href=\"https://cppinsights.io\">C++ Insights</a>. This gives us a view on what the compiler would do.","metadata":{},"id":"1a01b8b8"},{"cell_type":"markdown","source":"<img align=\"left\" src=\"../images/cppInsights01.png\" />","metadata":{},"id":"ca2aa233"},{"cell_type":"markdown","source":"Answers:\n- The instantiation of `StaticArray< float, 5 >` is implicit. Thus, it is lazy. The compiler knows that the member function `getSize()` exists and what its prototype is, but it will not generate machine code for it, as it is never called.\n- The class `StaticArray< float, 5 >` itself represents a datatype. While we can have multiple objects of this type, the template will only be instantiated once, for the same arguments.</br>\n *(Note: That's not necessarily true for multiple compilation units! If multiple instances get generated the linker takes care of uniqueness.)*","metadata":{},"id":"c9580c48"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"da055e27"}]} \ No newline at end of file +{"metadata":{"kernelspec":{"name":"xcpp17","display_name":"C++17","language":"C++17"},"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":"markdown","source":"# Template Programming: Basics","metadata":{},"id":"741a2490"},{"cell_type":"markdown","source":"## Motivation\n\nIn our first notebook **01_Overloading** we started with implementing two free-functions that returned the magnitude of their argument:","metadata":{"tags":[]},"id":"a61d218d"},{"cell_type":"code","source":"// Version for int\nint getMagnitude( int input ) {\n return input > 0 ? input : -input;\n}","metadata":{},"execution_count":1,"outputs":[],"id":"d4a7139f"},{"cell_type":"code","source":"// Version for double\ndouble getMagnitude( double input ) {\n return input > 0.0 ? input : -input;\n}","metadata":{},"execution_count":2,"outputs":[],"id":"cfbe1b82"},{"cell_type":"markdown","source":"Comparison of the code shows that there are three differences between the two functions:\n1. type of argument\n1. type of return value\n1. type of literal used\n\nThe last one can be eliminated by performing a static cast.","metadata":{},"id":"6f937e9d"},{"cell_type":"code","source":"// Version for double\ndouble getMagnitude( double input ) {\n return input > static_cast< double >( 0 ) ? input : -input;\n}","metadata":{},"execution_count":3,"outputs":[],"id":"0225265d"},{"cell_type":"markdown","source":"Now imagine that we want to have one function for every signed integer data type and each floating point type in C++, i.e.\n- signed char\n- signed short\n- signed int\n- signed long\n- signed long long\n- float\n- double\n- long double\n\nThis implies that we need to implement eight different versions of `getMagnitude`, which, however, are always following the same structure:\n\n```c++\n// Basic pattern of our free-function for a datatype TYPE is:\nTYPE getMagnitude( TYPE input ) {\n return input > static_cast< TYPE >( 0 ) ? input : -input;\n}\n```","metadata":{},"id":"13e637a9"},{"cell_type":"markdown","source":"---\n**Alternatives?**\n\nIt would be nice, if we did not have to write the (basically) same code multiple times\n- Repetition is dull and error-prone\n- We need to maintain (basically) the same code piece multiple times -> more work and error-prone\n\nFortunately there are ways to avoid this. The two prominent ones applicable in our situation are:\n1. (Automatic) Code Generation\n1. Generic Programming","metadata":{},"id":"3013b562"},{"cell_type":"markdown","source":"### (Automatic) Code Generation\n\nHere the idea is to let the computer generate the source code that the compiler sees itself following our instructions.\n\nFor this we need some (external) code generator. Often this is written in some other language than the generated code itself (e.g. in Python).\n\n> An example of this would be the \n<a href=\"https://fenicsproject.org\">FEniCS</a> project, a popular open-source computing platform for solving partial differential equations with Finite Elements. You describe your PDE problem in Python and from this efficient C++ code is generated, compiled and executed (very rough description :-) \n\nOften such generators are tuned to a specific field of problems and involve a so called\nDSL (domain specific language) to describe what is to be solved/generated.\n\n> For more on this see e.g. the publications of the <a href=\"https://www.exastencils.fau.de\">ExaStencils</a> project.\n\nAutomatic code generation plays an increasingly important role in high-performance scientific applications for **performance tuning** and **(performance) portability** (think CPU vs. GPU vs FPGA vs ...)","metadata":{"tags":[]},"id":"6dc7ea86"},{"cell_type":"markdown","source":"### 'Inline' Code Generation\nIn C++ it is possible to do some (limited) code generation on-the-fly using the capabilities of the **preprocessor**. An interesting overview on that can be found e.g. in <a href=\"https://link.springer.com/book/10.1007/978-3-662-48550-7\">C++ Metaprogrammierung</a>, Lemke, 2016, Springer.\n\nWe are going to take a look at this as an example, before proceeding to templates.","metadata":{},"id":"3191c55f"},{"cell_type":"code","source":"%%file getMag.cpp\n\n// we use a parameterised macro that represents the pattern of our function\n#define GETMAG( TYPE ) \\\n TYPE getMagnitude( TYPE value ) { \\\n return value > static_cast< TYPE >( 0 ) ? value : -value; \\\n}\n\n// Now we can let the preprocessor generate the source code by using the\n// macro with the corresponding datatype we need\nGETMAG( int )\nGETMAG( double )\nGETMAG( float )","metadata":{},"execution_count":4,"outputs":[{"name":"stdout","output_type":"stream","text":"Writing getMag.cpp\n"}],"id":"988d30ea"},{"cell_type":"markdown","source":"Now let us check how the result of running the preprocessor on that source code looks like:","metadata":{},"id":"625b1b11"},{"cell_type":"code","source":"!cpp getMag.cpp","metadata":{},"execution_count":5,"outputs":[{"name":"stdout","output_type":"stream","text":"# 1 \"getMag.cpp\"\n# 1 \"<built-in>\"\n# 1 \"<command-line>\"\n# 1 \"/usr/include/stdc-predef.h\" 1 3 4\n# 1 \"<command-line>\" 2\n# 1 \"getMag.cpp\"\n# 10 \"getMag.cpp\"\nint getMagnitude( int value ) { return value > static_cast< int >( 0 ) ? value : -value; }\ndouble getMagnitude( double value ) { return value > static_cast< double >( 0 ) ? value : -value; }\nfloat getMagnitude( float value ) { return value > static_cast< float >( 0 ) ? value : -value; }\n"}],"id":"8baeee45"},{"cell_type":"markdown","source":"---\nPreprocessing is an integral part of compilation of a C++ program (think of the `#include` directive) so this integrates seamlessly:","metadata":{},"id":"9ea5d77e"},{"cell_type":"code","source":"%%file getMag.cpp\n\n// we use a parameterised macro that represents the pattern of our function\n#define GETMAG( TYPE ) \\\n TYPE getMagnitude( TYPE value ) { \\\n return value < static_cast< TYPE >( 0 ) ? -value : value; \\\n}\n\n// Now we can let the preprocessor generate the source code by using the\n// macro with the corresponding datatype we need\nGETMAG( signed char )\nGETMAG( short )\nGETMAG( int )\nGETMAG( long )\nGETMAG( float )\nGETMAG( double )\n \n#include <iostream>\nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n std::cout << \"|\" << s << \"| = \" << getMagnitude( s ) << std::endl;\n std::cout << '|' << x << \"| = \" << getMagnitude( x ) << std::endl;\n std::cout << '|' << v << \"| = \" << getMagnitude( v ) << std::endl;\n}","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"Overwriting getMag.cpp\n"}],"id":"b1d7de95"},{"cell_type":"code","source":"!g++ -Wall -Wextra getMag.cpp","metadata":{},"execution_count":7,"outputs":[],"id":"b85f934b"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","output_type":"stream","text":"|-2| = 2\n|3.5| = 3.5\n|12.34| = 12.34\n"}],"id":"00684f47"},{"cell_type":"markdown","source":"The downside of this approach is that the C/C++ preprocessor has nearly no understanding of the language itself. Hence, it cannot perform any syntax checking or the like.\n\nWriting longer functions will become irksome, also, due to the line continuation stuff. *(and no syntax high-lighting or indentation ... by editors).*\n\nAlso we will need to **explicitely have a line for each `getMagnitude()` version** that might be used. Code will not be generated on a need-to-have basis.","metadata":{},"id":"6631c752"},{"cell_type":"markdown","source":"### Generic Programming\nThe idea here is to not use an external code generator, but instead provide functionality in the programming language itself that allows to accomplish what we want.\n\nFrom <a href=\"https://en.wikipedia.org/wiki/Generic_programming\">Wikipedia</a>:\n> Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. \n\nIn C++ the approach to support generic programming is to use **templates**.","metadata":{},"id":"dda3bd44"},{"cell_type":"markdown","source":"## C++ Template Basics\n\nSo how would we takle our problem with templates?\n\nInstead of multiple free-functions we implement a **single templated version** of it:","metadata":{},"id":"c9b5ee37"},{"cell_type":"code","source":"template< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}","metadata":{},"execution_count":1,"outputs":[],"id":"90ddfa90"},{"cell_type":"markdown","source":"When the compiler recognises that an instance of the templated function is needed for a specific data-type it will instantiate it (i.e. it will compile machine-code for this version): ","metadata":{},"id":"e2df86d3"},{"cell_type":"code","source":"#include <iostream>\nshort s = -5;\nstd::cout << \"|\" << s << \"| = \" << getMagnitude( s );","metadata":{},"execution_count":2,"outputs":[{"name":"stdout","text":"|-5| = 5","output_type":"stream"}],"id":"f5be2836"},{"cell_type":"markdown","source":"---\nA closer look at the **function template**:\n\n```c++\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value < static_cast< T >( 0 ) ? -value : value;\n}\n```\n\n* `template< typename T >`\n - informs the compiler that it is a templated function\n - in the example there is a single **template parameter** (often denoted as `T`, but that is\n just convention)\n - `typename` specifies `T` to be a **type** template parameter (`class` can be used interchangeably)\n - instead of a datatype a parameter can also be a basic datatype ( e.g. `template <int n>` ), or a *template template parameter*\n* In the body of the template declaration `T` is a typedef-name that aliases the type supplied when the template is instantiated","metadata":{},"id":"740ee0bd"},{"cell_type":"markdown","source":"---\nNow let's take a look at the **instantiation**:","metadata":{},"id":"d6754844-ef2d-496e-adab-1367a463c19f"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n \nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n getMagnitude( s );\n getMagnitude( x );\n getMagnitude( v );\n getMagnitude( -10L );\n}","metadata":{},"execution_count":36,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"8f2ab60c"},{"cell_type":"code","source":"!g++ -save-temps -c withTemplates.cpp","metadata":{},"execution_count":38,"outputs":[],"id":"96551727-c1b0-4b97-9bff-11e9eeac731e"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{},"execution_count":39,"outputs":[{"name":"stdout","text":"0000000000000000 T main\n0000000000000000 W double getMagnitude<double>(double)\n0000000000000000 W float getMagnitude<float>(float)\n0000000000000000 W int getMagnitude<int>(int)\n0000000000000000 W long getMagnitude<long>(long)\n","output_type":"stream"}],"id":"d3e0f1fc-f85a-4adc-a63f-ba7fdea02a2d"},{"cell_type":"markdown","source":"We observe that the object file contains instantiations of all four versions that are required in our source code, *but not more*. This is called **implicit instantiation**.\n\nA look at the file after preprocessing (`-save-temps` option) shows that no extra source code got generated. It really is something the compiler handles for us internally.","metadata":{},"id":"d63382c0-e766-422b-ba97-7824de507c3a"},{"cell_type":"code","source":"!cat withTemplates.ii","metadata":{},"execution_count":19,"outputs":[{"name":"stdout","output_type":"stream","text":"# 1 \"withTemplates.cpp\"\n# 1 \"<built-in>\"\n# 1 \"<command-line>\"\n# 1 \"/usr/include/stdc-predef.h\" 1 3 4\n# 1 \"<command-line>\" 2\n# 1 \"withTemplates.cpp\"\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\nint main() {\n\n short s = -2;\n float x = 3.5f;\n double v = 12.34;\n\n getMagnitude( s );\n getMagnitude( x );\n getMagnitude( v );\n getMagnitude( -10L );\n}\n"}],"id":"9c620720-b4cc-46a0-bde0-57452bcd3a0b"},{"cell_type":"markdown","source":"We can, however, also **explicitely** request the compiler to instantiate a version for a specific template argument:","metadata":{},"id":"5cfc8599"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\n// explicit instantiation\ntemplate float getMagnitude( float );\n\nint main() {\n\n // implicit instantiation\n getMagnitude( -2 );\n}","metadata":{},"execution_count":7,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"38aa85e7-83d9-4ae0-ac05-80cf187ee9f3"},{"cell_type":"code","source":"!g++ -c withTemplates.cpp","metadata":{},"execution_count":10,"outputs":[],"id":"76bfe507-bc01-457a-aeb5-38e2db572cac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{},"execution_count":11,"outputs":[{"name":"stdout","text":"0000000000000000 T main\n0000000000000000 W float getMagnitude<float>(float)\n0000000000000000 W int getMagnitude<int>(int)\n","output_type":"stream"}],"id":"e707d5e2-6d8d-4ee9-9046-b749d154146b"},{"cell_type":"markdown","source":"When might explicit instantiation be required?\n\nLet us modify our example to have the usual structure of *header file* (.hpp) and *source code file* (.cpp) and a separate *driver file*:","metadata":{},"id":"76ecc704-ac09-4343-b561-7817373aa1c9"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}","metadata":{},"execution_count":12,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"a854c3ce-1865-4ec1-b092-87be2a50d13f"},{"cell_type":"code","source":"%%file withTemplates.hpp\n\n// only a prototype\ntemplate< typename T > T getMagnitude( T value );","metadata":{},"execution_count":13,"outputs":[{"name":"stdout","text":"Writing withTemplates.hpp\n","output_type":"stream"}],"id":"b3b433db-8509-43fb-9946-579ffd4c5dfb"},{"cell_type":"code","source":"%%file driver.cpp\n\n#include \"withTemplates.hpp\"\nint main() {\n getMagnitude( -1.2345f );\n}","metadata":{},"execution_count":29,"outputs":[{"name":"stdout","text":"Overwriting driver.cpp\n","output_type":"stream"}],"id":"c96bbc17-3771-453b-9544-eae96dd936f0"},{"cell_type":"code","source":"!g++ driver.cpp","metadata":{},"execution_count":30,"outputs":[{"name":"stdout","text":"/tmp/ccbAB37j.o: In function `main':\ndriver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\ncollect2: error: ld returned 1 exit status\n","output_type":"stream"}],"id":"0cea5aa2-240b-493c-8afc-c560b0dff82c"},{"cell_type":"markdown","source":"Okay, that is not different from the usual. The source code of `getMagnitude()` is inside `withTemplates.cpp`. So let us also compile this source code file:","metadata":{},"id":"976e6ef4-ce58-4a4f-acf9-bff9d122923d"},{"cell_type":"code","source":"!g++ driver.cpp withTemplates.cpp","metadata":{},"execution_count":16,"outputs":[{"name":"stdout","text":"/tmp/ccNQjiNk.o: In function `main':\ndriver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\ncollect2: error: ld returned 1 exit status\n","output_type":"stream"}],"id":"e98425fb-dc85-4e89-9f9b-dc310b1c65bb"},{"cell_type":"markdown","source":"Still a problem! Why's that?","metadata":{},"id":"0ace12fa-d5a8-48ab-8d9b-250c71847812"},{"cell_type":"markdown","source":"---\n\n\n*intentionally left blank ;-)*\n\n\n---","metadata":{},"id":"bf20a5c9-5d48-405f-861e-afc7d699ff41"},{"cell_type":"markdown","source":"When we compile the driver the compiler will recognise that it need a version of `getMagnitude()` for `float`:","metadata":{},"id":"1517f452-ef0e-471a-82c0-880e829a51ff"},{"cell_type":"code","source":"!g++ -c driver.cpp","metadata":{},"execution_count":17,"outputs":[],"id":"846d3074-53c4-4dfe-94af-43b82f49d0a8"},{"cell_type":"code","source":"!nm -C driver.o","metadata":{},"execution_count":18,"outputs":[{"name":"stdout","text":" U _GLOBAL_OFFSET_TABLE_\n0000000000000000 T main\n U float getMagnitude<float>(float)\n","output_type":"stream"}],"id":"7f84b616-97d9-4ce0-a588-7bed26c4b4aa"},{"cell_type":"markdown","source":"However, it cannot instantiate it, because it does not see the definition of the template function, but only its prototype!\n\n- That is no error.\n- Finding an instance is then delegated to the linker. (Just as in a non-template case)\n\nOkay. So now let us compile the source code file and check the contents of the resulting object file:","metadata":{},"id":"43c992a6-7dca-4aab-8293-328b0cd8bf76"},{"cell_type":"code","source":"!g++ -c withTemplates.cpp","metadata":{},"execution_count":19,"outputs":[],"id":"15c7f98e-33fd-4bca-8293-5a19352c43ac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{},"execution_count":20,"outputs":[],"id":"5f6a4521-affc-4147-a33a-a7fd4f6051ce"},{"cell_type":"markdown","source":"Oops. Nothing. Why?\n\nBecause in the source code file no template instance is required and the template function itself cannot be compiled without an argument for the datatype!","metadata":{},"id":"b955ee26-e340-44e5-a264-66beda062321"},{"cell_type":"markdown","source":"---\nSplitting declaration and definition of a template function requires us to use explicit instantiation.","metadata":{},"id":"6bd45ab9-c3b9-4ec2-b093-1a2f327a0526"},{"cell_type":"code","source":"%%file withTemplates.cpp\n\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}\n\ntemplate float getMagnitude( float );","metadata":{},"execution_count":22,"outputs":[{"name":"stdout","text":"Overwriting withTemplates.cpp\n","output_type":"stream"}],"id":"e0aa1181-d4b2-4fc1-95fe-ead0f86b2d5b"},{"cell_type":"code","source":"!g++ -save-temps driver.cpp withTemplates.cpp","metadata":{},"execution_count":25,"outputs":[],"id":"e86d0de4-3e7e-48d1-8b2f-3a2b29e44842"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{},"execution_count":26,"outputs":[{"name":"stdout","text":"0000000000000000 W float getMagnitude<float>(float)\n","output_type":"stream"}],"id":"380b4117-4a81-45dc-a578-88f233d8726e"},{"cell_type":"markdown","source":"---\n\nRemark on **implicit instantiation**:\n\nThe advantage of implicit instantiation is that we only compile machine code for versions of functions that are actually used. This (potentially) reduces compile time and the size of the final executable.\n\nHowever, implicit template instantiation can also lead to **additional costs**. This occurs when the compiler needs to instantiate the same function version in different compilation units!\n - The compiler will generate the same machine code multiple times.\n - The linker needs to sort this out, i.e. select one of the multiple versions and remove the other.\n \nThis is what happens in the following example:","metadata":{},"id":"cf415ea0-e550-432c-b04b-31dd4c4e35d9"},{"cell_type":"code","source":"%%file impl.cpp\n\n// full definition of template function\ntemplate< typename T >\nT getMagnitude( T value ) {\n return value > static_cast< T >( 0 ) ? value : -value;\n}","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"Overwriting impl.cpp\n","output_type":"stream"}],"id":"5e345b44-6617-49cd-acc5-c286b6ba8c1f"},{"cell_type":"code","source":"%%file unitA.cpp\n\n// include needed to allow for implicit instantiation\n#include \"impl.cpp\"\n\nint functionInUnitA() {\n return getMagnitude(-3);\n}","metadata":{"trusted":true},"execution_count":13,"outputs":[{"name":"stdout","text":"Overwriting unitA.cpp\n","output_type":"stream"}],"id":"e0093449-1bda-4c56-b020-2931b9881e1a"},{"cell_type":"code","source":"%%file unitB.cpp\n\n#include \"impl.cpp\"\n\nint functionInUnitA(); // prototyping\n\nint functionInUnitB() {\n return getMagnitude(72);\n}\n\nint main() {\n functionInUnitA();\n functionInUnitB();\n}","metadata":{"trusted":true},"execution_count":14,"outputs":[{"name":"stdout","text":"Overwriting unitB.cpp\n","output_type":"stream"}],"id":"51365f4e-5739-4b9d-93de-9af47ea7fb35"},{"cell_type":"code","source":"!g++ -save-temps unitA.cpp unitB.cpp","metadata":{"trusted":true},"execution_count":15,"outputs":[],"id":"89b35bb7-33b3-48e1-a7d5-947c19a3b03a"},{"cell_type":"code","source":"!nm -C unitA.o unitB.o","metadata":{"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":"\nunitA.o:\n0000000000000000 W int getMagnitude<int>(int)\n0000000000000000 T functionInUnitA()\n\nunitB.o:\n U _GLOBAL_OFFSET_TABLE_\n0000000000000010 T main\n0000000000000000 W int getMagnitude<int>(int)\n U functionInUnitA()\n0000000000000000 T functionInUnitB()\n","output_type":"stream"}],"id":"99721a1f-1f99-4f97-9db7-026c34764f47"},{"cell_type":"markdown","source":"So we have two versions of `int getMagnitude<int>(int)`, one in each object file. However, of course, there is only one in the executable:","metadata":{},"id":"f8746c67-fc8e-47a7-aef8-828fce5951b4"},{"cell_type":"code","source":"!nm -C a.out","metadata":{"trusted":true},"execution_count":19,"outputs":[{"name":"stdout","text":"0000000000201010 B __bss_start\n0000000000201010 b completed.7698\n w __cxa_finalize@@GLIBC_2.2.5\n0000000000201000 D __data_start\n0000000000201000 W data_start\n0000000000000520 t deregister_tm_clones\n00000000000005b0 t __do_global_dtors_aux\n0000000000200df8 t __do_global_dtors_aux_fini_array_entry\n0000000000201008 D __dso_handle\n0000000000200e00 d _DYNAMIC\n0000000000201010 D _edata\n0000000000201018 B _end\n00000000000006c4 T _fini\n00000000000005f0 t frame_dummy\n0000000000200df0 t __frame_dummy_init_array_entry\n000000000000088c r __FRAME_END__\n0000000000200fc0 d _GLOBAL_OFFSET_TABLE_\n w __gmon_start__\n00000000000006d4 r __GNU_EH_FRAME_HDR\n00000000000004b8 T _init\n0000000000200df8 t __init_array_end\n0000000000200df0 t __init_array_start\n00000000000006d0 R _IO_stdin_used\n w _ITM_deregisterTMCloneTable\n w _ITM_registerTMCloneTable\n00000000000006c0 T __libc_csu_fini\n0000000000000650 T __libc_csu_init\n U __libc_start_main@@GLIBC_2.2.5\n000000000000062e T main\n0000000000000560 t register_tm_clones\n00000000000004f0 T _start\n0000000000201010 D __TMC_END__\n000000000000060a W int getMagnitude<int>(int)\n00000000000005fa T functionInUnitA()\n000000000000061e T functionInUnitB()\n","output_type":"stream"}],"id":"335f3097-f272-4bc8-8f6e-7c808773c326"},{"cell_type":"markdown","source":"### Template Classes\n\nNaturally in C++ free-functions are not the only aspect we can templatise. We can also do so for member functions of classes and complete classes. The latter we have already seen e.g. with the STL containers.","metadata":{},"id":"9368884e"},{"cell_type":"markdown","source":"As an example let us write a small wrapper class for a static array (note that the useful variant of this would be `std::array` from the STL).","metadata":{},"id":"f91b42be"},{"cell_type":"code","source":"template< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};","metadata":{},"execution_count":33,"outputs":[],"id":"5d7941c5"},{"cell_type":"code","source":"StaticArray< double, 3 > shortVec;\nshortVec.getSize()","metadata":{},"execution_count":34,"outputs":[{"execution_count":34,"output_type":"execute_result","data":{"text/plain":"3"},"metadata":{}}],"id":"69a7c099"},{"cell_type":"markdown","source":"Observations:\n1. The syntax for the template class definition is similar to that of template functions. We have the normal class part with a `template< 'argument list' >` in front of it.\n1. Template parameters are not restricted to type parameters. We can also use **non-type parameters**:\n - integer types (`int`, `short`, `bool`, ...)\n - enum value of an enumeration types\n - floating-point types (since C++20)\n - pointers and references \nthe argument for instantiation then is a value of that type.\n1. We do not need to store the size `N` of the array in the class as an attribute. Instead we can use the template parameter which will be replaced by the argument value provided at instantiation.\n1. Contrary to our previous examples we need to specifiy the template arguments explicitely for the instantiation to work. The compiler cannot determine them from our use of the default constructor. So **CTAD (Class Template Argument Deduction)** will not work.","metadata":{},"id":"db743abf"},{"cell_type":"code","source":"StaticArray longVec;","metadata":{},"execution_count":40,"outputs":[{"name":"stderr","text":"input_line_17:2:2: error: use of class template 'StaticArray' requires template arguments\n StaticArray longVec;\n ^\ninput_line_12:2:8: note: template is declared here\nstruct StaticArray {\n ^\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"a2978a5c"},{"cell_type":"code","source":"// In C++17 the following example of CTAD works; compiler deduces type\n// of vector entries from that of the initialiser list.\n#include <vector>\nstd::vector v{1,2,3};\nstd::vector x{0.1,2.0};","metadata":{},"execution_count":2,"outputs":[],"id":"acbd3d96-da66-4fb6-9732-66508a2544fe"},{"cell_type":"markdown","source":"### Lazy vs Eager Instantiation","metadata":{},"id":"a8f52ca7"},{"cell_type":"markdown","source":"In C++ **implicit instantiation is lazy**. In the case of a template class this means that member functions that are not called do not get instantiated! \n\n**Explicit instantiation**, on the other hand, is eager.\n\nWe can see this from the following (slightly extended) example taken from \nRainer Grimm's blog <a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++ in der Praxis</a>.","metadata":{},"id":"9f21c5d4"},{"cell_type":"code","source":"%%file lazy.cpp\n\n#include <cmath>\n#include <string>\n\ntemplate< typename T >\nstruct Number {\n int absValue() {\n return std::abs( val );\n }\n T val{};\n};\n\n// force explicit instantiation of struct (this is eager!) (A)\n// template struct Number< std::string >;\n\nint main() {\n\n // results in implicit instantiation (this is lazy!)\n Number< std::string > num;\n\n // results in instantiation of member function (B)\n // num.absValue();\n}","metadata":{},"execution_count":7,"outputs":[{"name":"stdout","text":"Overwriting lazy.cpp\n","output_type":"stream"}],"id":"a06c3b23"},{"cell_type":"code","source":"!g++ lazy.cpp","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","text":"lazy.cpp: In instantiation of ‘int Number<T>::absValue() [with T = std::__cxx11::basic_string<char>]’:\nlazy.cpp:14:17: required from here\nlazy.cpp:8:20: error: no matching function for call to ‘abs(std::__cxx11::basic_string<char>&)’\n return std::abs( val );\n ~~~~~~~~^~~~~~~\nIn file included from /usr/include/c++/7/cmath:47:0,\n from lazy.cpp:2:\n/usr/include/c++/7/bits/std_abs.h:102:3: note: candidate: constexpr __float128 std::abs(__float128)\n abs(__float128 __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:102:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘__float128’\n/usr/include/c++/7/bits/std_abs.h:84:3: note: candidate: constexpr __int128 std::abs(__int128)\n abs(__GLIBCXX_TYPE_INT_N_0 __x) { return __x >= 0 ? __x : -__x; }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:84:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘__int128’\n/usr/include/c++/7/bits/std_abs.h:78:3: note: candidate: constexpr long double std::abs(long double)\n abs(long double __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:78:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long double’\n/usr/include/c++/7/bits/std_abs.h:74:3: note: candidate: constexpr float std::abs(float)\n abs(float __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:74:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘float’\n/usr/include/c++/7/bits/std_abs.h:70:3: note: candidate: constexpr double std::abs(double)\n abs(double __x)\n ^~~\n/usr/include/c++/7/bits/std_abs.h:70:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘double’\n/usr/include/c++/7/bits/std_abs.h:61:3: note: candidate: long long int std::abs(long long int)\n abs(long long __x) { return __builtin_llabs (__x); }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:61:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long long int’\n/usr/include/c++/7/bits/std_abs.h:56:3: note: candidate: long int std::abs(long int)\n abs(long __i) { return __builtin_labs(__i); }\n ^~~\n/usr/include/c++/7/bits/std_abs.h:56:3: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘long int’\nIn file included from /usr/include/c++/7/bits/std_abs.h:38:0,\n from /usr/include/c++/7/cmath:47,\n from lazy.cpp:2:\n/usr/include/stdlib.h:837:12: note: candidate: int abs(int)\n extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;\n ^~~\n/usr/include/stdlib.h:837:12: note: no known conversion for argument 1 from ‘std::__cxx11::basic_string<char>’ to ‘int’\n","output_type":"stream"}],"id":"06c4ffb9"},{"cell_type":"markdown","source":"Let us check this for our previous example, too.","metadata":{},"id":"c89daa16"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};\n\nint main() {\n\n StaticArray< float, 5 > shortVec;\n StaticArray< short, 100 > longVec;\n StaticArray< float, 5 > shortVec2;\n std::cout << longVec.getSize();\n}","metadata":{},"execution_count":2,"outputs":[],"id":"05d338d8"},{"cell_type":"markdown","source":"Questions:\n- Will `getSize()` be instantiated for `StaticArray< float, 5 >`?\n- How many times will `StaticArray< float, 5 >` be instantiated?\n\nIn order to answer these let us feed the code to the source-to-source translator\n<a href=\"https://cppinsights.io\">C++ Insights</a>. This gives us a view on what the compiler would do.","metadata":{},"id":"1a01b8b8"},{"cell_type":"markdown","source":"<img align=\"left\" src=\"../images/cppInsights01.png\" />","metadata":{},"id":"ca2aa233"},{"cell_type":"markdown","source":"Answers:\n- The instantiation of `StaticArray< float, 5 >` is implicit. Thus, it is lazy. The compiler knows that the member function `getSize()` exists and what its prototype is, but it will not generate machine code for it, as it is never called.\n- The class `StaticArray< float, 5 >` itself represents a datatype. While we can have multiple objects of this type, the template will only be instantiated once, for the same arguments.</br>\n *(Note: That's not necessarily true for multiple compilation units! If multiple instances get generated the linker takes care of uniqueness. See example above for template functions.)*","metadata":{},"id":"c9580c48"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"da055e27"}]} \ No newline at end of file diff --git a/notebooks/12_Template_Basics_cont.ipynb b/notebooks/12_Template_Basics_cont.ipynb index 2bbb328..0715bc3 100644 --- a/notebooks/12_Template_Basics_cont.ipynb +++ b/notebooks/12_Template_Basics_cont.ipynb @@ -1 +1 @@ -{"metadata":{"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":"markdown","source":"# Template Programming: Basics (cont.)","metadata":{},"id":"677e4115"},{"cell_type":"markdown","source":"## Splitting Declaration and Definition of Member Functions","metadata":{},"id":"5bd22367"},{"cell_type":"markdown","source":"In our previous notebook we played with the `StaticArray` template class. This was a wrapper class around a static array:","metadata":{},"id":"c4959f15"},{"cell_type":"code","source":"template< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};","metadata":{"trusted":true},"execution_count":2,"outputs":[],"id":"06794358"},{"cell_type":"markdown","source":"Let us make this a little more useful, by adding an access operator with out-of-bounds checks. How could we do this?","metadata":{},"id":"c4b0ee2c"},{"cell_type":"code","source":"\n\n\n\n","metadata":{},"execution_count":null,"outputs":[],"id":"e9a99d23"},{"cell_type":"code","source":"#include <cassert>\n\ntemplate< typename T, unsigned int N >\nclass StaticArray {\n\npublic:\n int getSize() const {\n return N;\n };\n \n T& operator[] ( unsigned int idx ) {\n assert( idx < N );\n return data[idx];\n }\n\nprivate:\n T data[N];\n};","metadata":{},"execution_count":2,"outputs":[],"id":"62c9e7e8"},{"cell_type":"markdown","source":"Changes:\n* We make it a `class` instead of a `struct` (not strictly necessary).\n* Make internal data array inaccessible from the outside.\n* This size cannot be negative, so we use an usigned integer datatype.\n* Provided an access operator and perform bounds checking via an assertion.\n\nLet's check read and write access:","metadata":{},"id":"6f31e164"},{"cell_type":"code","source":"#include <iostream>\n\nStaticArray< float, 5 > shortVec;\nshortVec[4] = 55.123f;\nstd::cout << \"shortVec[4] = \" << shortVec[4] << std::endl;","metadata":{},"execution_count":3,"outputs":[{"name":"stdout","output_type":"stream","text":"shortVec[4] = 55.123\n"}],"id":"b4ad7433"},{"cell_type":"markdown","source":"---\nThe member functions `getSize()` and `operator[]` are short (source code wise). Thus, we would usually tend to have them implemented inside the class itself.\n1. Allows potential inlining be the compiler.\n1. Does not hinder implicit instantiation.\n\nFor demonstration purposes let us split declaration and definition now.","metadata":{},"id":"f7c16a8f"},{"cell_type":"code","source":"template< typename T, unsigned int N >\nclass StaticArray {\n\npublic:\n int getSize() const;\n T& operator[] ( unsigned int idx );\n\nprivate:\n T data[N];\n};","metadata":{},"execution_count":4,"outputs":[],"id":"a73f9a53"},{"cell_type":"markdown","source":"Below we see the syntax for an external (from the class) definition of a member function:","metadata":{},"id":"0cfb30e7"},{"cell_type":"code","source":"template< typename T, unsigned int N >\nint StaticArray< T, N >::getSize() const {\n return N;\n};\n\ntemplate< typename T, unsigned int N >\nT& StaticArray< T, N >::operator[] ( unsigned int idx ) {\n assert( idx < N );\n return data[idx];\n}","metadata":{},"execution_count":5,"outputs":[],"id":"9916b961"},{"cell_type":"code","source":"StaticArray< int, 10 > iVec;\n\nfor( unsigned int i = 0; i < 10; ++i ) {\n iVec[i] = i;\n}\n\nstd::cout << \"iVec[7] = \" << iVec[7] << std::endl;","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"iVec[7] = 7\n"}],"id":"d177f32c"},{"cell_type":"markdown","source":"## Specialisation (\"Same, same, but different\")","metadata":{},"id":"27b84650"},{"cell_type":"markdown","source":"The idea of templates is to avoid having to implement essentially the same class or algorithm multiple times, if only e.g. the type of data items we store or operate on changes.\n\nHowever, we often can encounter situations where some details might still depend on the datatype or where we would prefer to change an aspect for a specific case.\n\nOne way to take care of this is to use **template specialisation**. This allows us to implement a specific version for one specific set of template arguments.","metadata":{},"id":"4f0528df"},{"cell_type":"markdown","source":"### Class Template Specialisation\nAs an example let us implement a class to store an 8-tuple of elements.","metadata":{},"id":"052571db"},{"cell_type":"code","source":"#include <iostream>\n#include <array>\n#include <algorithm>\n\ntemplate< typename T >\nclass tuple8 {\n\npublic:\n tuple8( std::array< T, 8 > elements ) {\n std::copy( std::begin(elements), std::end(elements), std::begin(data) );\n }\n void show() {\n for( unsigned int k = 0; k < 8; ++k ) {\n std::cout << k+1 << \": \" << data[k] << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n T data[8];\n};","metadata":{"trusted":true},"execution_count":3,"outputs":[],"id":"682be60b"},{"cell_type":"markdown","source":"Let's test our class:","metadata":{},"id":"7b42efde"},{"cell_type":"code","source":"int main() {\n\n tuple8<int> tp1( { 42, 13, 11, 7, 6, 30, 49, 25 } );\n tp1.show();\n\n tuple8<double> tp2( { 8.1, 7.2, 6.3, 5.4, 4.5, 3.6, 2.7, 1.8 } );\n tp2.show();\n\n tuple8< std::string > tp3( { \"morgen\", \"ist\", \"heute\", \"gestern\",\n \"tomorrow\", \"today\", \"will be\", \"yesterday\" } );\n tp3.show();\n\n tuple8<bool> tp4( {false, false, true, false, true, true, false, true} );\n std::cout << std::boolalpha;\n tp4.show();\n}","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"a346abf2"},{"cell_type":"code","source":"main();","metadata":{"scrolled":true},"execution_count":9,"outputs":[{"name":"stdout","output_type":"stream","text":"1: 42\n2: 13\n3: 11\n4: 7\n5: 6\n6: 30\n7: 49\n8: 25\n\n1: 8.1\n2: 7.2\n3: 6.3\n4: 5.4\n5: 4.5\n6: 3.6\n7: 2.7\n8: 1.8\n\n1: morgen\n2: ist\n3: heute\n4: gestern\n5: tomorrow\n6: today\n7: will be\n8: yesterday\n\n1: false\n2: false\n3: true\n4: false\n5: true\n6: true\n7: false\n8: true\n\n"}],"id":"52806ab9"},{"cell_type":"markdown","source":"Next, we examine the memory footprint of our classes","metadata":{},"id":"41114644-caea-4722-a2ca-bf1283bd7048"},{"cell_type":"code","source":"std::cout << \"tuple8<int> needs \" << sizeof( tuple8<int> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":11,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<int> needs 32 bytes\n"}],"id":"d90c6867"},{"cell_type":"code","source":"std::cout << \"tuple8<double> needs \" << sizeof( tuple8<double> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":12,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<double> needs 64 bytes\n"}],"id":"ed0bd98a"},{"cell_type":"code","source":"std::cout << \"tuple8<std::string> needs \" << sizeof( tuple8<std::string> )\n << \" bytes\" << std::endl;","metadata":{},"execution_count":13,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<std::string> needs 256 bytes\n"}],"id":"1641275c"},{"cell_type":"markdown","source":"To understand the last output let us also examine the footprint of an `std::string`","metadata":{},"id":"fb3b39cd"},{"cell_type":"code","source":"sizeof( std::string )","metadata":{},"execution_count":14,"outputs":[{"execution_count":14,"output_type":"execute_result","data":{"text/plain":"32"},"metadata":{}}],"id":"b357e7e4"},{"cell_type":"markdown","source":"Now what out an 8-tuple of bools?","metadata":{},"id":"1dc909c2"},{"cell_type":"code","source":"std::cout << \"tuple8<bool> needs \" << sizeof( tuple8<bool> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":16,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<bool> needs 8 bytes\n"}],"id":"f40defbf"},{"cell_type":"markdown","source":"Now that's actually a little wasteful. Eight bools could also be represented by eight single bits.\n\nC++ allows us to perform operations on single bits and we have 1-byte datatypes. So we could do a special implementation, i.e. a **specialisation**, to the `bool` case.\n\nA specialisation always needs to be *done after* the primary definition of the template class. In our case this was:","metadata":{},"id":"fe555684"},{"cell_type":"code","source":"#include <iostream>\n#include <array>\n#include <algorithm>\n\ntemplate< typename T >\nclass tuple8 {\n\npublic:\n tuple8( std::array< T, 8 > elements ) {\n std::copy( std::begin(elements), std::end(elements), std::begin(data) );\n }\n void show() {\n for( unsigned int k = 0; k < 8; ++k ) {\n std::cout << k+1 << \": \" << data[k] << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n T data[8];\n};","metadata":{"trusted":true},"execution_count":5,"outputs":[],"id":"51de1f21"},{"cell_type":"markdown","source":"Syntactically the specialisation of our template class looks as follows:\n\n```c++\ntemplate<>\nclass tuple8< bool > {\n // our special implementation goes here\n};\n```\n- Of course we need the `template` keyword, so that the compiler knows what it's dealing with.\n- However, when we specialise there are no arguments between the `< >`\n- The arguments detailing which case we want to specialise follow then after the class name.\n\nOkay now let us implement the specifics for our example:","metadata":{},"id":"769720f4"},{"cell_type":"code","source":"template<>\nclass tuple8< bool > {\n\npublic:\n\n tuple8( std::array< bool, 8 > elements ) {\n data = 0;\n for( unsigned int k = 0; k < 8; ++k ) {\n // cast the bool to an integer and perform a bit-shift\n // the bitwise OR will place the bit in data at the correct\n // position (we store right-to-left)\n data |= static_cast< unsigned char >( elements[k] ) << k;\n }\n }\n\n void show() {\n std::cout << std::boolalpha;\n \n for( unsigned int k = 0; k < 8; ++k ) {\n // use a bit-mask for extraction, the mask is L for bit k,\n // and O for all others\n unsigned char mask = 1u << k;\n \n // bitwise AND with our mask filters out all bits, but the k-th one\n // finally cast integer back to bool\n std::cout << k+1 << \": \" << static_cast< bool >(data & mask) << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n // single byte to store our 8 bits/bools\n unsigned char data;\n};","metadata":{"trusted":true},"execution_count":6,"outputs":[],"id":"064726c3"},{"cell_type":"markdown","source":"Test drive with new version:","metadata":{},"id":"8c59cda5"},{"cell_type":"code","source":"int main() {\n\n tuple8<int> tp1( { 42, 13, 11, 7, 6, 30, 49, 25 } );\n tp1.show();\n\n tuple8<double> tp2( { 8.1, 7.2, 6.3, 5.4, 4.5, 3.6, 2.7, 1.8 } );\n tp2.show();\n\n tuple8< std::string > tp3( { \"morgen\", \"ist\", \"heute\", \"gestern\",\n \"tomorrow\", \"today\", \"will be\", \"yesterday\" } );\n tp3.show();\n\n tuple8<bool> tp4( {false, false, true, false, true, true, false, true} );\n // std::cout << std::boolalpha; no longer needed, specialisation takes care of this\n tp4.show();\n}\nmain();","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"1: 42\n2: 13\n3: 11\n4: 7\n5: 6\n6: 30\n7: 49\n8: 25\n\n1: 8.1\n2: 7.2\n3: 6.3\n4: 5.4\n5: 4.5\n6: 3.6\n7: 2.7\n8: 1.8\n\n1: morgen\n2: ist\n3: heute\n4: gestern\n5: tomorrow\n6: today\n7: will be\n8: yesterday\n\n1: false\n2: false\n3: true\n4: false\n5: true\n6: true\n7: false\n8: true\n\n","output_type":"stream"}],"id":"45d97f2e"},{"cell_type":"markdown","source":"Just to verify let us query the memory size of our specialisation:","metadata":{},"id":"4facecac-015d-48b2-bc1e-f798136d408a"},{"cell_type":"code","source":"std::cout << \"tuple8<bool> needs \" << sizeof( tuple8<bool> ) << \" bytes\" << std::endl;","metadata":{"trusted":true},"execution_count":8,"outputs":[{"name":"stdout","text":"tuple8<bool> needs 1 bytes\n","output_type":"stream"}],"id":"8d70b27a"},{"cell_type":"markdown","source":"**Remarks:**\n- Specialisation allows to take care of special cases in our template implementation.\n- A specialisation is a complete standalone implementation, we do not get to re-use any member function or attribute of the primary template case.\n- In fact a specialisation can be completely different to the generic case.\n\nAs an example let us introduce a template function to report the memory footprint of our different 8-tuples.","metadata":{},"id":"ea2ff7a2"},{"cell_type":"code","source":"template< typename T >\nvoid checkSize() {\n size_t size = sizeof( tuple8< T > );\n std::cout << \"tuple8< ??? > needs \"\n << size << \" byte\" << (size > 1 ? \"s\" : \"\") << std::endl;\n}","metadata":{},"execution_count":26,"outputs":[],"id":"792e040f"},{"cell_type":"code","source":"checkSize<int>();\ncheckSize<bool>();","metadata":{},"execution_count":27,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8< ??? > needs 32 bytes\ntuple8< ??? > needs 1 byte\n"}],"id":"662b90a0"},{"cell_type":"markdown","source":"What can we put in place of the `???` to print the datatype?","metadata":{},"id":"f7d41172"},{"cell_type":"code","source":"// Could use intrinsic C++ typeinfo stuff:\n\n#include <typeinfo>\n\nstd::cout << \"A bool has type \" << typeid( bool ).name() << std::endl;\n\nstd::cout << \"A tuple8< std::string > has type \" << typeid( tuple8< std::string > ).name()\n << std::endl;","metadata":{"trusted":true},"execution_count":10,"outputs":[{"name":"stdout","text":"A bool has type b\nA tuple8< std::string > has type N11__cling_N566tuple8INSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE\n","output_type":"stream"}],"id":"8293f53e"},{"cell_type":"markdown","source":"Problems with this:\n- `name` is compiler dependent\n- and might not be very readable (especially for complicated types)\n---\nWe can, however, implement our own type-to-string translation, based on template specialisation.","metadata":{},"id":"b272157b"},{"cell_type":"code","source":"// We start with an empty primary template class\ntemplate< typename T >\nstruct TupleTrait{};","metadata":{"trusted":true},"execution_count":11,"outputs":[],"id":"0ad6c871"},{"cell_type":"code","source":"// now we specialise this for the types we need\ntemplate<>\nstruct TupleTrait< bool >{\n // with C++17 we could also use:\n // inline static const std::string elemType{ \"bool\" };\n static std::string elemType() { return \"bool\"; }\n};\n\ntemplate<>\nstruct TupleTrait< int >{\n static std::string elemType() { return \"int\"; }\n};\n\ntemplate<>\nstruct TupleTrait< double >{\n static std::string elemType() { return \"double\"; }\n};\n\ntemplate<>\nstruct TupleTrait< std::string >{\n static std::string elemType() { return \"std::string\"; }\n\n};","metadata":{"trusted":true},"execution_count":12,"outputs":[],"id":"f78be101"},{"cell_type":"markdown","source":"---\n**Interjection:** \n* What is the meaning of the `static` qualifier for our `elemType()` member function?\n* A static member of a class (function or attribute) exists independent of any object of the class and only once in the program.\n* We can access it using the scope qualifier: ```TupleTrait<int>::elemType()```\n---\nNow we can update ","metadata":{},"id":"624a6f64"},{"cell_type":"code","source":"template< typename T >\nvoid checkSize() {\n size_t size = sizeof( tuple8< T > );\n std::cout << \"tuple8< \" << TupleTrait< T >::elemType() << \" > needs \"\n << size << \" byte\" << (size > 1 ? \"s\" : \"\") << std::endl;\n}","metadata":{"trusted":true},"execution_count":13,"outputs":[],"id":"593aa90c"},{"cell_type":"code","source":"checkSize< bool >();\ncheckSize< int >();\ncheckSize< double >();\ncheckSize< std::string >();","metadata":{"trusted":true},"execution_count":14,"outputs":[{"name":"stdout","text":"tuple8< bool > needs 1 byte\ntuple8< int > needs 32 bytes\ntuple8< double > needs 64 bytes\ntuple8< std::string > needs 256 bytes\n","output_type":"stream"}],"id":"5c944dda"},{"cell_type":"markdown","source":"### Partial Specialisation and Resolution Rules","metadata":{},"id":"487b9a10"},{"cell_type":"markdown","source":"We again borrow the idea for an example from Rainer Grimm's blog <a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++ in der Praxis</a>.\n\nAssume we have implemented a class `tinyMatrix` for storing a small dense matrix. We do not want to fix the datatype or its dimension and do a templated implementation:","metadata":{},"id":"e9619e90"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate< typename T, unsigned int Nrows, unsigned int Ncols = Nrows >\nclass tinyMatrix {\npublic:\n void dims() { std::cout << Nrows << \" x \" << Ncols << std::endl; }\n // details skipped\n};","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"7d45242f"},{"cell_type":"markdown","source":"Note that template parameters can have **default values**, which can even refer to other template paramters. So our matrix by default will be symmetric:","metadata":{},"id":"1f9fb4d9"},{"cell_type":"code","source":"tinyMatrix< double, 2, 5 > dMat;\ntinyMatrix< float, 3 > fMat;\n\ndMat.dims();\nfMat.dims();","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"2 x 5\n3 x 3\n","output_type":"stream"}],"id":"f4fde99d"},{"cell_type":"markdown","source":"We can perform partial specialisations by fixing a subset of the template parameters and implementing these sub-families in a special way.\n\nFor all $2 \\times 2$ matrices, e.g., we know a simple way to compute the determinant. ","metadata":{},"id":"e575305a"},{"cell_type":"code","source":"template< typename T >\nclass tinyMatrix< T, 2, 2 >{\npublic:\n tinyMatrix() { std::cout << \"2x2 matrix of type \" << typeid( T ).name() << std::endl; }\n};\n\n// due to our default value this would also work:\n// template< typename T >\n// class tinyMatrix< T, 2 >{};\n\n// specialisation for column vectors\ntemplate< typename T, int Nrows >\nclass tinyMatrix< T, Nrows, 1 > {\npublic:\n tinyMatrix() { std::cout << \"column vector of dim \" << Nrows << std::endl; } \n};\n\n// full specialisation for a certain int matrix\ntemplate<>\nclass tinyMatrix< int, 3, 5 > {\npublic:\n tinyMatrix() { std::cout << \"Special 3x5 int matrix\" << std::endl; }\n};","metadata":{"trusted":true},"execution_count":3,"outputs":[],"id":"bb3db438"},{"cell_type":"markdown","source":"Instantiation works the same as before:","metadata":{},"id":"577ddc25"},{"cell_type":"code","source":"tinyMatrix< double, 2 > squareDoubleMat;\ntinyMatrix< float, 2 > squareFloatMat;","metadata":{"trusted":true},"execution_count":4,"outputs":[{"name":"stdout","text":"2x2 matrix of type d\n2x2 matrix of type f\n","output_type":"stream"}],"id":"e5fa2816"},{"cell_type":"code","source":"tinyMatrix< short, 10, 1 > sVec;","metadata":{"trusted":true},"execution_count":5,"outputs":[{"name":"stdout","text":"column vector of dim 10\n","output_type":"stream"}],"id":"8ff595ea"},{"cell_type":"code","source":"tinyMatrix< int, 3, 5 > sVec;","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"Special 3x5 int matrix\n"}],"id":"06e860c4"},{"cell_type":"code","source":"tinyMatrix< int, 2, 5 > sVec; // uses the primary template","metadata":{"trusted":true},"execution_count":6,"outputs":[],"id":"0b081ae7"},{"cell_type":"markdown","source":"**Rules for Instantiation:**\n* When there is no specialisation the primary template is used.\n* When there is only one specialisation (and it fits) that one is used.\n* When there are multiple fitting specialisations the *most specialised one* is used.\n\nA template A is more specialised than a template B, if\n* B can accept all arguments that A can accept.\n* B can accept arguments that A cannot accept. ","metadata":{},"id":"c7083e66"},{"cell_type":"markdown","source":"### Function Template Specialisation\nFunction templates can in principle also be specialised. However:\n- A partial specialisation of function templates is not possible.\n- Resolution of overloading can create clashes with specialised function templates.\n- We can always implement special cases instead by overloading.\nAs a consequence of the latter two points the **C++ Core Guidelines** state:\n> T.144: Don't specialize function templates\n\nIn order to see what can go wrong we look at a variant of an example originally presented by Peter Dimov and Dave Abrahams (we again borrow from Rainer Grimm's blog for this).","metadata":{},"id":"fef57ef2"},{"cell_type":"code","source":"// (1) - primary template\ntemplate< typename T > \nstd::string getTypeName( T ) {\n return \"unknown\";\n}\n\n// (2) - primary template that overloads (1)\ntemplate< typename T >\nstd::string getTypeName( T* ) {\n return \"pointer\";\n}\n\n// (3) explicit specialization of (2)\ntemplate<>\nstd::string getTypeName( int* ) {\n return \"int pointer\";\n}","metadata":{"trusted":true},"execution_count":7,"outputs":[],"id":"c9e3fe11-63a1-4b85-94aa-aed48d43fa56"},{"cell_type":"markdown","source":"Now let us test this by calling **`getTypeName()`** with an argument that is an **int pointer** to see which variant is going to be used.","metadata":{},"id":"e14ce8e7-b26e-40f9-9347-3082ee904cc1"},{"cell_type":"code","source":"#include <iostream>\n\nint main() {\n \n std::cout << std::endl;\n \n int* p = nullptr;\n \n std::cout << \"getTypeName(p): \" << getTypeName(p) << std::endl; \n \n std::cout << std::endl;\n \n}\n\nmain();","metadata":{"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"\ngetTypeName(p): int pointer\n\n","output_type":"stream"}],"id":"7b176f92-f4e6-46e5-96b8-f026ee7b2ba2"},{"cell_type":"markdown","source":"Okay. Given the type of argument that is the result we would expect.\n \nNow let us make a change to the implementation and see whether the outcome is affected. For this we call, as before, **`getTypeName2()`** with an argument of type **int pinter**.","metadata":{},"id":"a9a2b2af-706f-4588-8e08-5a2687062c18"},{"cell_type":"code","source":"%%file demo2.cpp\n\n#include <string>\n#include <iostream>\n\n// (4) - primary template\ntemplate< typename T >\nstd::string getTypeName2( T ) {\n return \"unknown\";\n}\n\n// (5) - explicit specialization of (4)\ntemplate<> // <-- try commenting this line in\nstd::string getTypeName2( int* ) {\n return \"int pointer\";\n}\n\n// (6) - primary template that overloads (4)\ntemplate< typename T >\nstd::string getTypeName2( T* ) {\n return \"pointer\";\n}\n\nint main() {\n \n std::cout << std::endl;\n \n int *p = nullptr;\n \n std::cout << \"getTypeName2(p): \" << getTypeName2(p) << std::endl; \n \n std::cout << std::endl;\n \n}","metadata":{"trusted":true},"execution_count":31,"outputs":[{"name":"stdout","text":"Overwriting demo2.cpp\n","output_type":"stream"}],"id":"f675c57f-d55d-46ed-8a5d-47435b576440"},{"cell_type":"code","source":"!g++ demo2.cpp","metadata":{"trusted":true},"execution_count":32,"outputs":[],"id":"3feea32f-ce2b-4626-a854-39b53efbb807"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":33,"outputs":[{"name":"stdout","text":"\ngetTypeName2(p): pointer\n\n","output_type":"stream"}],"id":"65db8054-bcdc-42e1-ace1-15e94dfc0df0"},{"cell_type":"markdown","source":"Oops, that is not what we wanted. Why did this happen?\n\n**Explanation:** \nThe problem results from the following. For overload resolution the compiler only looks at\n- functions\n- primary templates\n\nTemplate specialisations are not taken into account.\n\n*First example:* \nThe primary template (2) is a better fit than primary template (1). Thus (2) is selected. However, for this a fitting\nspecialisation (3) exists. This is the function finally being called.\n\n*Second example:* \nHere the primary template (6) is a better fit than the primary template (4). Thus (6) is selected and the better fitting specialisation (5) of (4) is ignored.","metadata":{},"id":"afc05319-85b5-424e-97af-4d32eaa404b7"},{"cell_type":"markdown","source":"## Template Classes and Inheritance","metadata":{},"id":"b10bda50-c663-44af-b7e1-5133eaa0a4bc"},{"cell_type":"markdown","source":"Template classes can form a part of an inheritance relationship. They can serve either as base or as derived class. However, some special aspects need to be taken into account in this situation.","metadata":{},"id":"6db3e7f6-d274-4734-aa2d-c9c9b098496f"},{"cell_type":"markdown","source":"### Example 1: Templatisation of a derived class","metadata":{},"id":"556fdcd3-9407-43b8-b708-8c46dcd4e1c5"},{"cell_type":"code","source":"%%file inherit1.cpp\n\n#include <type_traits>\n#include <iostream>\n\nclass Parent{};\n\ntemplate< typename T >\nclass Child : public Parent {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n#if 0\n std::cout << \"Child is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child >::value\n << std::endl;\n#endif\n\n std::cout << \"Child< int > is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child< int > >::value\n << std::endl;\n\n std::cout << \"Child< float > is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child< float > >::value\n << std::endl;\n}","metadata":{"trusted":true},"execution_count":8,"outputs":[{"name":"stdout","text":"Overwriting inherit1.cpp\n","output_type":"stream"}],"id":"f5e1615d-911f-4ce4-bdd5-475674330706"},{"cell_type":"code","source":"!g++ inherit1.cpp","metadata":{"trusted":true},"execution_count":9,"outputs":[],"id":"eaab3059-85fd-4200-aee5-8be0fd5c5639"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":10,"outputs":[{"name":"stdout","text":"Child< int > is offspring of Parent? That's true\nChild< float > is offspring of Parent? That's true\n","output_type":"stream"}],"id":"b33feb73-dc23-4f0a-ba83-f9f80ae8c28f"},{"cell_type":"markdown","source":"Note that each instance of the `Child` template class is a child of the `Parent` base class.","metadata":{},"id":"5d3dc301-5e44-4114-bb65-2fb269af332e"},{"cell_type":"markdown","source":"### Example 2: Deriving from a Templated Base Class","metadata":{},"id":"26d9b143-8f9c-45d2-8fc2-fbe27cdec514"},{"cell_type":"code","source":"%%file inherit2.cpp\n\n#include <iostream>\n\ntemplate< typename T >\nclass Parent{};\n\nclass Child : public Parent< double > {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n std::cout << \"Child is offspring of Parent< double >? That's \"\n << std::is_base_of< Parent< double >, Child >::value\n << std::endl;\n\n std::cout << \"Child is offspring of Parent< int >? That's \"\n << std::is_base_of< Parent< int >, Child >::value\n << std::endl;\n\n}","metadata":{"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"Overwriting inherit2.cpp\n","output_type":"stream"}],"id":"1d52c9a9-3be5-45bf-b40b-8e7eaf178973"},{"cell_type":"code","source":"!g++ inherit2.cpp; ./a.out","metadata":{"trusted":true},"execution_count":12,"outputs":[{"name":"stdout","text":"Child is offspring of Parent< double >? That's true\nChild is offspring of Parent< int >? That's false\n","output_type":"stream"}],"id":"6afe5ee3-0b5b-48a5-93e8-2e85de7ac3ee"},{"cell_type":"markdown","source":"Different arguments result in different instances of the base class. `Child` is only a child of the specific instance it inherits from. This is important e.g. in the case of specialisation.","metadata":{},"id":"816805a7-856a-47e0-bb59-eb25ac9227c9"},{"cell_type":"markdown","source":"### Example 3: 'Templated' Hierarchy","metadata":{},"id":"acb18101-539d-4a00-b919-9ddda5428b26"},{"cell_type":"code","source":"%%file inherit3.cpp\n\n#include <iostream>\n\ntemplate< typename T >\nclass Parent{};\n\ntemplate< typename T >\nclass Child : public Parent< T > {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n std::cout << \"Child< short > is offspring of Parent< short >? That's \"\n << std::is_base_of< Parent< short >, Child< short > >::value\n << std::endl;\n\n std::cout << \"Child< float > is offspring of Parent< short >? That's \"\n << std::is_base_of< Parent< short >, Child< float > >::value\n << std::endl;\n\n}","metadata":{"trusted":true},"execution_count":17,"outputs":[{"name":"stdout","text":"Overwriting inherit3.cpp\n","output_type":"stream"}],"id":"8ab3caf0-78ce-4ad2-8113-c6943c12dc8f"},{"cell_type":"code","source":"!g++ inherit3.cpp; ./a.out","metadata":{"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":"Child< short > is offspring of Parent< short >? That's true\nChild< float > is offspring of Parent< short >? That's false\n","output_type":"stream"}],"id":"a399b900-0cc0-46b2-bdfc-3b992c000e7e"},{"cell_type":"markdown","source":"In this case both the base and the derived class are templated. Only instances with the same argument have an inheritance relationship.","metadata":{},"id":"f9f3271f-53b2-4584-a39d-84efcb87938d"},{"cell_type":"markdown","source":"### Member Functions\nLet us take a look at potential pitfalls of inheritance.\n\nThe following example works without problems. We have a templated base class and inherit from a specific instance of it.","metadata":{},"id":"aaf4a1e1-d3d6-47f0-9d3a-590090bc19ee"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {\n }\n};\n\nclass Child1 : public Parent< double > {\npublic:\n void phoneParent() {\n ringMe();\n }\n};","metadata":{"trusted":true},"execution_count":19,"outputs":[{"name":"stdout","text":"Overwriting members.cpp\n","output_type":"stream"}],"id":"5995c963-7dec-4fcd-98a4-d7fb3fe233e7"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{"trusted":true},"execution_count":20,"outputs":[],"id":"dd6f2208-c6a8-40dd-a420-f769f43ac962"},{"cell_type":"markdown","source":"Now let us change the derived class to be templated and inherit from a base class with the same parameter.","metadata":{},"id":"b51cb8a0-6dcc-4095-85b5-57aeef973e03"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {\n }\n};\n\ntemplate< typename T >\nclass Child2 : public Parent< T > {\npublic:\n void phoneParent() {\n ringMe();\n }\n T doSomething( T& ref );\n};","metadata":{"trusted":true},"execution_count":23,"outputs":[{"name":"stdout","text":"Overwriting members.cpp\n","output_type":"stream"}],"id":"e544a69e-185b-4006-b655-5ad1280126ac"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{"trusted":true},"execution_count":24,"outputs":[{"name":"stdout","text":"members.cpp: In member function ‘void Child2<T>::phoneParent()’:\nmembers.cpp:13:5: error: there are no arguments to ‘ringMe’ that depend on a template parameter, so a declaration of ‘ringMe’ must be available [-fpermissive]\n ringMe();\n ^~~~~~\nmembers.cpp:13:5: note: (if you use ‘-fpermissive’, G++ will accept your code, but allowing the use of an undeclared name is deprecated)\n","output_type":"stream"}],"id":"4b019b32-ba1a-4b0f-bfb5-49ab3274ddff"},{"cell_type":"markdown","source":"Hmmmm ... what goes wrong here?","metadata":{},"id":"e4f59d30-c5f0-4f29-8035-f2eee3e65db2"},{"cell_type":"markdown","source":"The problem is a result of <a href=\"https://stackoverflow.com/questions/7767626/two-phase-lookup-explanation-needed\">Two-phase Lookup</a>. The latter describes the fact that the compiler examines a template at least twice:\n\n1. The first time (S1), when it checks the syntactical correctness of the template definition and resolves non-dependent names (see below).\n1. A second time (S2), when it attempts to instantiate the template for a given set of template arguments. (To ensure that everything, e.g. a function call, works for the given specific arguments)\n\nOur problem results from the fact that `ringMe()` is a **non-dependent name**, i.e. it does not depend on the template parameter `T`. The compiler needs to resolve a non-dependend name in compile stage (S1). However, this implies that it will not look for `ringMe()` in the `Parent` base class. Since there is neither a declaration of`ringMe()` in the scope of the derived class, nor as a free function, compilation fails.\n\nThe member function `doSomething`, on the other hand, is a **dependent name**. The latter will be resolved only in stage (S2). Then the compiler also knows from which instance of `Parent` our child class actually derives.","metadata":{},"id":"3eaa22dd-5f89-4a49-830c-d0f332a7d6ac"},{"cell_type":"markdown","source":"How can we still call the base class member function in the derived class? There are multiple ways to solve that conundrum:","metadata":{},"id":"c8601795-7c5b-4eaa-9fbe-c8e280b80034"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {\n }\n};\n\ntemplate< typename T >\nclass Child3 : public Parent< T > {\npublic:\n\n void phoneParent1() {\n this->ringMe(); // (a)\n }\n\n void phoneParent2() {\n Parent< T >::ringMe(); // (b)\n }\n\n using Parent< T >::ringMe; // (c)\n void phoneParent3() {\n ringMe();\n }\n\n};","metadata":{},"execution_count":null,"outputs":[],"id":"78992fb6-52ca-4e7b-8c73-0d9812664222"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{},"execution_count":null,"outputs":[],"id":"7c113d13-b93f-476e-a7bd-7e363d327109"},{"cell_type":"markdown","source":"* In variant (a) we make the name dependent, by telling the compiler that `ringMe()` is a member function of the current template class (inherited in our case from the base class).\n* In variant (b) we make it explicit, that the function is to be found in the scope of the templated base class, by *fully qualifying* it. This would break a virtual dispatch, though.\n* Variant (c) uses an 'import' into the current scope.","metadata":{},"id":"19274a1c-bd34-438a-b602-62455b94a0e6"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"225919ae-e243-4fde-b193-647efcb382f5"}]} \ No newline at end of file +{"metadata":{"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":"markdown","source":"# Template Programming: Basics (cont.)","metadata":{},"id":"677e4115"},{"cell_type":"markdown","source":"## Splitting Declaration and Definition of Member Functions","metadata":{},"id":"5bd22367"},{"cell_type":"markdown","source":"In our previous notebook we played with the `StaticArray` template class. This was a wrapper class around a static array:","metadata":{},"id":"c4959f15"},{"cell_type":"code","source":"template< typename T, int N >\nstruct StaticArray {\n int getSize() { return N; };\n T data[N];\n};","metadata":{},"execution_count":2,"outputs":[],"id":"06794358"},{"cell_type":"markdown","source":"Let us make this a little more useful, by adding an access operator with out-of-bounds checks. How could we do this?","metadata":{},"id":"c4b0ee2c"},{"cell_type":"code","source":"\n\n\n\n","metadata":{},"execution_count":null,"outputs":[],"id":"e9a99d23"},{"cell_type":"code","source":"#include <cassert>\n\ntemplate< typename T, unsigned int N >\nclass StaticArray {\n\npublic:\n int getSize() const {\n return N;\n };\n \n T& operator[] ( unsigned int idx ) {\n assert( idx < N );\n return data[idx];\n }\n\nprivate:\n T data[N];\n};","metadata":{},"execution_count":2,"outputs":[],"id":"62c9e7e8"},{"cell_type":"markdown","source":"Changes:\n* We make it a `class` instead of a `struct` (not strictly necessary).\n* Make internal data array inaccessible from the outside.\n* This size cannot be negative, so we use an usigned integer datatype.\n* Provided an access operator and perform bounds checking via an assertion.\n\nLet's check read and write access:","metadata":{},"id":"6f31e164"},{"cell_type":"code","source":"#include <iostream>\n\nStaticArray< float, 5 > shortVec;\nshortVec[4] = 55.123f;\nstd::cout << \"shortVec[4] = \" << shortVec[4] << std::endl;","metadata":{},"execution_count":3,"outputs":[{"name":"stdout","output_type":"stream","text":"shortVec[4] = 55.123\n"}],"id":"b4ad7433"},{"cell_type":"markdown","source":"---\nThe member functions `getSize()` and `operator[]` are short (source code wise). Thus, we would usually tend to have them implemented inside the class itself.\n1. Allows potential inlining be the compiler.\n1. Does not hinder implicit instantiation.\n\nFor demonstration purposes let us split declaration and definition now.","metadata":{},"id":"f7c16a8f"},{"cell_type":"code","source":"template< typename T, unsigned int N >\nclass StaticArray {\n\npublic:\n int getSize() const;\n T& operator[] ( unsigned int idx );\n\nprivate:\n T data[N];\n};","metadata":{},"execution_count":4,"outputs":[],"id":"a73f9a53"},{"cell_type":"markdown","source":"Below we see the syntax for an external (from the class) definition of a member function:","metadata":{},"id":"0cfb30e7"},{"cell_type":"code","source":"template< typename T, unsigned int N >\nint StaticArray< T, N >::getSize() const {\n return N;\n};\n\ntemplate< typename T, unsigned int N >\nT& StaticArray< T, N >::operator[] ( unsigned int idx ) {\n assert( idx < N );\n return data[idx];\n}","metadata":{},"execution_count":5,"outputs":[],"id":"9916b961"},{"cell_type":"code","source":"StaticArray< int, 10 > iVec;\n\nfor( unsigned int i = 0; i < 10; ++i ) {\n iVec[i] = i;\n}\n\nstd::cout << \"iVec[7] = \" << iVec[7] << std::endl;","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"iVec[7] = 7\n"}],"id":"d177f32c"},{"cell_type":"markdown","source":"## Specialisation (\"Same, same, but different\")","metadata":{},"id":"27b84650"},{"cell_type":"markdown","source":"The idea of templates is to avoid having to implement essentially the same class or algorithm multiple times, if only e.g. the type of data items we store or operate on changes.\n\nHowever, we often can encounter situations where some details might still depend on the datatype or where we would prefer to change an aspect for a specific case.\n\nOne way to take care of this is to use **template specialisation**. This allows us to implement a specific version for one specific set of template arguments.","metadata":{},"id":"4f0528df"},{"cell_type":"markdown","source":"### Class Template Specialisation\nAs an example let us implement a class to store an 8-tuple of elements.","metadata":{},"id":"052571db"},{"cell_type":"code","source":"#include <iostream>\n#include <array>\n#include <algorithm>\n\ntemplate< typename T >\nclass tuple8 {\n\npublic:\n tuple8( std::array< T, 8 > elements ) {\n std::copy( std::begin(elements), std::end(elements), std::begin(data) );\n }\n void show() {\n for( unsigned int k = 0; k < 8; ++k ) {\n std::cout << k+1 << \": \" << data[k] << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n T data[8];\n};","metadata":{},"execution_count":3,"outputs":[],"id":"682be60b"},{"cell_type":"markdown","source":"Let's test our class:","metadata":{},"id":"7b42efde"},{"cell_type":"code","source":"int main() {\n\n tuple8<int> tp1( { 42, 13, 11, 7, 6, 30, 49, 25 } );\n tp1.show();\n\n tuple8<double> tp2( { 8.1, 7.2, 6.3, 5.4, 4.5, 3.6, 2.7, 1.8 } );\n tp2.show();\n\n tuple8< std::string > tp3( { \"morgen\", \"ist\", \"heute\", \"gestern\",\n \"tomorrow\", \"today\", \"will be\", \"yesterday\" } );\n tp3.show();\n\n tuple8<bool> tp4( {false, false, true, false, true, true, false, true} );\n std::cout << std::boolalpha;\n tp4.show();\n}","metadata":{},"execution_count":4,"outputs":[],"id":"a346abf2"},{"cell_type":"code","source":"main();","metadata":{"scrolled":true},"execution_count":9,"outputs":[{"name":"stdout","output_type":"stream","text":"1: 42\n2: 13\n3: 11\n4: 7\n5: 6\n6: 30\n7: 49\n8: 25\n\n1: 8.1\n2: 7.2\n3: 6.3\n4: 5.4\n5: 4.5\n6: 3.6\n7: 2.7\n8: 1.8\n\n1: morgen\n2: ist\n3: heute\n4: gestern\n5: tomorrow\n6: today\n7: will be\n8: yesterday\n\n1: false\n2: false\n3: true\n4: false\n5: true\n6: true\n7: false\n8: true\n\n"}],"id":"52806ab9"},{"cell_type":"markdown","source":"Next, we examine the memory footprint of our classes","metadata":{},"id":"41114644-caea-4722-a2ca-bf1283bd7048"},{"cell_type":"code","source":"std::cout << \"tuple8<int> needs \" << sizeof( tuple8<int> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":11,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<int> needs 32 bytes\n"}],"id":"d90c6867"},{"cell_type":"code","source":"std::cout << \"tuple8<double> needs \" << sizeof( tuple8<double> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":12,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<double> needs 64 bytes\n"}],"id":"ed0bd98a"},{"cell_type":"code","source":"std::cout << \"tuple8<std::string> needs \" << sizeof( tuple8<std::string> )\n << \" bytes\" << std::endl;","metadata":{},"execution_count":13,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<std::string> needs 256 bytes\n"}],"id":"1641275c"},{"cell_type":"markdown","source":"To understand the last output let us also examine the footprint of an `std::string`","metadata":{},"id":"fb3b39cd"},{"cell_type":"code","source":"sizeof( std::string )","metadata":{},"execution_count":14,"outputs":[{"execution_count":14,"output_type":"execute_result","data":{"text/plain":"32"},"metadata":{}}],"id":"b357e7e4"},{"cell_type":"markdown","source":"Now what out an 8-tuple of bools?","metadata":{},"id":"1dc909c2"},{"cell_type":"code","source":"std::cout << \"tuple8<bool> needs \" << sizeof( tuple8<bool> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":16,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8<bool> needs 8 bytes\n"}],"id":"f40defbf"},{"cell_type":"markdown","source":"Now that's actually a little wasteful. Eight bools could also be represented by eight single bits.\n\nC++ allows us to perform operations on single bits and we have 1-byte datatypes. So we could do a special implementation, i.e. a **specialisation**, to the `bool` case.\n\nA specialisation always needs to be *done after* the primary definition of the template class. In our case this was:","metadata":{},"id":"fe555684"},{"cell_type":"code","source":"#include <iostream>\n#include <array>\n#include <algorithm>\n\ntemplate< typename T >\nclass tuple8 {\n\npublic:\n tuple8( std::array< T, 8 > elements ) {\n std::copy( std::begin(elements), std::end(elements), std::begin(data) );\n }\n void show() {\n for( unsigned int k = 0; k < 8; ++k ) {\n std::cout << k+1 << \": \" << data[k] << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n T data[8];\n};","metadata":{},"execution_count":5,"outputs":[],"id":"51de1f21"},{"cell_type":"markdown","source":"Syntactically the specialisation of our template class looks as follows:\n\n```c++\ntemplate<>\nclass tuple8< bool > {\n // our special implementation goes here\n};\n```\n- Of course we need the `template` keyword, so that the compiler knows what it's dealing with.\n- However, when we specialise there are no arguments between the `< >`\n- The arguments detailing which case we want to specialise follow then after the class name.\n\nOkay now let us implement the specifics for our example:","metadata":{},"id":"769720f4"},{"cell_type":"code","source":"template<>\nclass tuple8< bool > {\n\npublic:\n\n tuple8( std::array< bool, 8 > elements ) {\n data = 0;\n for( unsigned int k = 0; k < 8; ++k ) {\n // cast the bool to an integer and perform a bit-shift\n // the bitwise OR will place the bit in data at the correct\n // position (we store right-to-left)\n data |= static_cast< unsigned char >( elements[k] ) << k;\n }\n }\n\n void show() {\n std::cout << std::boolalpha;\n \n for( unsigned int k = 0; k < 8; ++k ) {\n // use a bit-mask for extraction, the mask is L for bit k,\n // and O for all others\n unsigned char mask = 1u << k;\n \n // bitwise AND with our mask filters out all bits, but the k-th one\n // finally cast integer back to bool\n std::cout << k+1 << \": \" << static_cast< bool >(data & mask) << '\\n';\n }\n std::cout << std::endl;\n }\n\nprivate:\n // single byte to store our 8 bits/bools\n unsigned char data;\n};","metadata":{},"execution_count":6,"outputs":[],"id":"064726c3"},{"cell_type":"markdown","source":"Test drive with new version:","metadata":{},"id":"8c59cda5"},{"cell_type":"code","source":"int main() {\n\n tuple8<int> tp1( { 42, 13, 11, 7, 6, 30, 49, 25 } );\n tp1.show();\n\n tuple8<double> tp2( { 8.1, 7.2, 6.3, 5.4, 4.5, 3.6, 2.7, 1.8 } );\n tp2.show();\n\n tuple8< std::string > tp3( { \"morgen\", \"ist\", \"heute\", \"gestern\",\n \"tomorrow\", \"today\", \"will be\", \"yesterday\" } );\n tp3.show();\n\n tuple8<bool> tp4( {false, false, true, false, true, true, false, true} );\n // std::cout << std::boolalpha; no longer needed, specialisation takes care of this\n tp4.show();\n}\nmain();","metadata":{},"execution_count":7,"outputs":[{"name":"stdout","text":"1: 42\n2: 13\n3: 11\n4: 7\n5: 6\n6: 30\n7: 49\n8: 25\n\n1: 8.1\n2: 7.2\n3: 6.3\n4: 5.4\n5: 4.5\n6: 3.6\n7: 2.7\n8: 1.8\n\n1: morgen\n2: ist\n3: heute\n4: gestern\n5: tomorrow\n6: today\n7: will be\n8: yesterday\n\n1: false\n2: false\n3: true\n4: false\n5: true\n6: true\n7: false\n8: true\n\n","output_type":"stream"}],"id":"45d97f2e"},{"cell_type":"markdown","source":"Just to verify let us query the memory size of our specialisation:","metadata":{},"id":"4facecac-015d-48b2-bc1e-f798136d408a"},{"cell_type":"code","source":"std::cout << \"tuple8<bool> needs \" << sizeof( tuple8<bool> ) << \" bytes\" << std::endl;","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","text":"tuple8<bool> needs 1 bytes\n","output_type":"stream"}],"id":"8d70b27a"},{"cell_type":"markdown","source":"**Remarks:**\n- Specialisation allows to take care of special cases in our template implementation.\n- A specialisation is a complete standalone implementation, we do not get to re-use any member function or attribute of the primary template case.\n- In fact a specialisation can be completely different to the generic case.\n\nAs an example let us introduce a template function to report the memory footprint of our different 8-tuples.","metadata":{},"id":"ea2ff7a2"},{"cell_type":"code","source":"template< typename T >\nvoid checkSize() {\n size_t size = sizeof( tuple8< T > );\n std::cout << \"tuple8< ??? > needs \"\n << size << \" byte\" << (size > 1 ? \"s\" : \"\") << std::endl;\n}","metadata":{},"execution_count":26,"outputs":[],"id":"792e040f"},{"cell_type":"code","source":"checkSize<int>();\ncheckSize<bool>();","metadata":{},"execution_count":27,"outputs":[{"name":"stdout","output_type":"stream","text":"tuple8< ??? > needs 32 bytes\ntuple8< ??? > needs 1 byte\n"}],"id":"662b90a0"},{"cell_type":"markdown","source":"What can we put in place of the `???` to print the datatype?","metadata":{},"id":"f7d41172"},{"cell_type":"code","source":"// Could use intrinsic C++ typeinfo stuff:\n\n#include <typeinfo>\n\nstd::cout << \"A bool has type \" << typeid( bool ).name() << std::endl;\n\nstd::cout << \"A tuple8< std::string > has type \" << typeid( tuple8< std::string > ).name()\n << std::endl;","metadata":{},"execution_count":10,"outputs":[{"name":"stdout","text":"A bool has type b\nA tuple8< std::string > has type N11__cling_N566tuple8INSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE\n","output_type":"stream"}],"id":"8293f53e"},{"cell_type":"markdown","source":"Problems with this:\n- `name` is compiler dependent\n- and might not be very readable (especially for complicated types)\n---\nWe can, however, implement our own type-to-string translation, based on template specialisation.","metadata":{},"id":"b272157b"},{"cell_type":"code","source":"// We start with an empty primary template class\ntemplate< typename T >\nstruct TupleTrait{};","metadata":{},"execution_count":11,"outputs":[],"id":"0ad6c871"},{"cell_type":"code","source":"// now we specialise this for the types we need\ntemplate<>\nstruct TupleTrait< bool >{\n // with C++17 we could also use:\n // inline static const std::string elemType{ \"bool\" };\n static std::string elemType() { return \"bool\"; }\n};\n\ntemplate<>\nstruct TupleTrait< int >{\n static std::string elemType() { return \"int\"; }\n};\n\ntemplate<>\nstruct TupleTrait< double >{\n static std::string elemType() { return \"double\"; }\n};\n\ntemplate<>\nstruct TupleTrait< std::string >{\n static std::string elemType() { return \"std::string\"; }\n\n};","metadata":{},"execution_count":12,"outputs":[],"id":"f78be101"},{"cell_type":"markdown","source":"---\n**Interjection:** \n* What is the meaning of the `static` qualifier for our `elemType()` member function?\n* A static member of a class (function or attribute) exists independent of any object of the class and only once in the program.\n* We can access it using the scope qualifier: ```TupleTrait<int>::elemType()```\n---\nNow we can update ","metadata":{},"id":"624a6f64"},{"cell_type":"code","source":"template< typename T >\nvoid checkSize() {\n size_t size = sizeof( tuple8< T > );\n std::cout << \"tuple8< \" << TupleTrait< T >::elemType() << \" > needs \"\n << size << \" byte\" << (size > 1 ? \"s\" : \"\") << std::endl;\n}","metadata":{},"execution_count":13,"outputs":[],"id":"593aa90c"},{"cell_type":"code","source":"checkSize< bool >();\ncheckSize< int >();\ncheckSize< double >();\ncheckSize< std::string >();","metadata":{},"execution_count":14,"outputs":[{"name":"stdout","text":"tuple8< bool > needs 1 byte\ntuple8< int > needs 32 bytes\ntuple8< double > needs 64 bytes\ntuple8< std::string > needs 256 bytes\n","output_type":"stream"}],"id":"5c944dda"},{"cell_type":"markdown","source":"### Partial Specialisation and Resolution Rules","metadata":{},"id":"487b9a10"},{"cell_type":"markdown","source":"We again borrow the idea for an example from Rainer Grimm's blog <a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++ in der Praxis</a>.\n\nAssume we have implemented a class `tinyMatrix` for storing a small dense matrix. We do not want to fix the datatype or its dimension and do a templated implementation:","metadata":{},"id":"e9619e90"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate< typename T, unsigned int Nrows, unsigned int Ncols = Nrows >\nclass tinyMatrix {\npublic:\n void dims() { std::cout << Nrows << \" x \" << Ncols << std::endl; }\n // details skipped\n};","metadata":{},"execution_count":1,"outputs":[],"id":"7d45242f"},{"cell_type":"markdown","source":"Note that template parameters can have **default values**, which can even refer to other template paramters. So our matrix by default will be quadratic:","metadata":{},"id":"1f9fb4d9"},{"cell_type":"code","source":"tinyMatrix< double, 2, 5 > dMat;\ntinyMatrix< float, 3 > fMat;\n\ndMat.dims();\nfMat.dims();","metadata":{},"execution_count":2,"outputs":[{"name":"stdout","text":"2 x 5\n3 x 3\n","output_type":"stream"}],"id":"f4fde99d"},{"cell_type":"markdown","source":"We can perform partial specialisations by fixing a subset of the template parameters and implementing these sub-families in a special way.\n\nFor all $2 \\times 2$ matrices, e.g., we know a simple way to compute the determinant. ","metadata":{},"id":"e575305a"},{"cell_type":"code","source":"template< typename T >\nclass tinyMatrix< T, 2, 2 >{\npublic:\n tinyMatrix() { std::cout << \"2x2 matrix of type \" << typeid( T ).name() << std::endl; }\n};\n\n// due to our default value this would also work:\n// template< typename T >\n// class tinyMatrix< T, 2 >{};\n\n// specialisation for column vectors\ntemplate< typename T, int Nrows >\nclass tinyMatrix< T, Nrows, 1 > {\npublic:\n tinyMatrix() { std::cout << \"column vector of dim \" << Nrows << std::endl; } \n};\n\n// full specialisation for a certain int matrix\ntemplate<>\nclass tinyMatrix< int, 3, 5 > {\npublic:\n tinyMatrix() { std::cout << \"Special 3x5 int matrix\" << std::endl; }\n};","metadata":{},"execution_count":3,"outputs":[],"id":"bb3db438"},{"cell_type":"markdown","source":"Instantiation works the same as before:","metadata":{},"id":"577ddc25"},{"cell_type":"code","source":"tinyMatrix< double, 2 > squareDoubleMat;\ntinyMatrix< float, 2 > squareFloatMat;","metadata":{},"execution_count":4,"outputs":[{"name":"stdout","text":"2x2 matrix of type d\n2x2 matrix of type f\n","output_type":"stream"}],"id":"e5fa2816"},{"cell_type":"code","source":"tinyMatrix< short, 10, 1 > sVec;","metadata":{},"execution_count":5,"outputs":[{"name":"stdout","text":"column vector of dim 10\n","output_type":"stream"}],"id":"8ff595ea"},{"cell_type":"code","source":"tinyMatrix< int, 3, 5 > sVec;","metadata":{},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"Special 3x5 int matrix\n"}],"id":"06e860c4"},{"cell_type":"code","source":"tinyMatrix< int, 2, 5 > sVec; // uses the primary template","metadata":{},"execution_count":6,"outputs":[],"id":"0b081ae7"},{"cell_type":"markdown","source":"**Rules for Instantiation:**\n* When there is no specialisation the primary template is used.\n* When there is only one specialisation (and it fits) that one is used.\n* When there are multiple fitting specialisations the *most specialised one* is used.\n\nA template A is more specialised than a template B, if\n* B can accept all arguments that A can accept.\n* B can accept arguments that A cannot accept. ","metadata":{},"id":"c7083e66"},{"cell_type":"markdown","source":"### Function Template Specialisation\nFunction templates can in principle also be specialised. However:\n- A partial specialisation of function templates is not possible.\n- Resolution of overloading can create clashes with specialised function templates.\n- We can always implement special cases instead by overloading.\nAs a consequence of the latter two points the **C++ Core Guidelines** state:\n> T.144: Don't specialize function templates\n\nIn order to see what can go wrong we look at a variant of an example originally presented by Peter Dimov and Dave Abrahams (we again borrow from Rainer Grimm's blog for this).","metadata":{},"id":"fef57ef2"},{"cell_type":"code","source":"// (1) - primary template\ntemplate< typename T > \nstd::string getTypeName( T ) {\n return \"unknown\";\n}\n\n// (2) - primary template that overloads (1)\ntemplate< typename T >\nstd::string getTypeName( T* ) {\n return \"pointer\";\n}\n\n// (3) explicit specialization of (2)\ntemplate<>\nstd::string getTypeName( int* ) {\n return \"int pointer\";\n}","metadata":{},"execution_count":7,"outputs":[],"id":"c9e3fe11-63a1-4b85-94aa-aed48d43fa56"},{"cell_type":"markdown","source":"Now let us test this by calling **`getTypeName()`** with an argument that is an **int pointer** to see which variant is going to be used.","metadata":{},"id":"e14ce8e7-b26e-40f9-9347-3082ee904cc1"},{"cell_type":"code","source":"#include <iostream>\n\nint main() {\n \n std::cout << std::endl;\n \n int* p = nullptr;\n \n std::cout << \"getTypeName(p): \" << getTypeName(p) << std::endl; \n \n std::cout << std::endl;\n \n}\n\nmain();","metadata":{},"execution_count":11,"outputs":[{"name":"stdout","text":"\ngetTypeName(p): int pointer\n\n","output_type":"stream"}],"id":"7b176f92-f4e6-46e5-96b8-f026ee7b2ba2"},{"cell_type":"markdown","source":"Okay. Given the type of argument that is the result we would expect.\n \nNow let us make a change to the implementation and see whether the outcome is affected. For this we call, as before, **`getTypeName2()`** with an argument of type **int pinter**.","metadata":{},"id":"a9a2b2af-706f-4588-8e08-5a2687062c18"},{"cell_type":"code","source":"%%file demo2.cpp\n\n#include <string>\n#include <iostream>\n\n// (4) - primary template\ntemplate< typename T >\nstd::string getTypeName2( T ) {\n return \"unknown\";\n}\n\n// (5) - explicit specialization of (4)\ntemplate<> // <-- try commenting this line in\nstd::string getTypeName2( int* ) {\n return \"int pointer\";\n}\n\n// (6) - primary template that overloads (4)\ntemplate< typename T >\nstd::string getTypeName2( T* ) {\n return \"pointer\";\n}\n\nint main() {\n \n std::cout << std::endl;\n \n int *p = nullptr;\n \n std::cout << \"getTypeName2(p): \" << getTypeName2(p) << std::endl; \n \n std::cout << std::endl;\n \n}","metadata":{},"execution_count":31,"outputs":[{"name":"stdout","text":"Overwriting demo2.cpp\n","output_type":"stream"}],"id":"f675c57f-d55d-46ed-8a5d-47435b576440"},{"cell_type":"code","source":"!g++ demo2.cpp","metadata":{},"execution_count":32,"outputs":[],"id":"3feea32f-ce2b-4626-a854-39b53efbb807"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":33,"outputs":[{"name":"stdout","text":"\ngetTypeName2(p): pointer\n\n","output_type":"stream"}],"id":"65db8054-bcdc-42e1-ace1-15e94dfc0df0"},{"cell_type":"markdown","source":"Oops, that is not what we wanted. Why did this happen?\n\n**Explanation:** \nThe problem results from the following. For overload resolution the compiler only looks at\n- functions\n- primary templates\n\nTemplate specialisations are not taken into account.\n\n*First example:* \nThe primary template (2) is a better fit than primary template (1). Thus (2) is selected. However, for this a fitting\nspecialisation (3) exists. This is the function finally being called.\n\n*Second example:* \nHere the primary template (6) is a better fit than the primary template (4). Thus (6) is selected and the better fitting specialisation (5) of (4) is ignored.","metadata":{},"id":"afc05319-85b5-424e-97af-4d32eaa404b7"},{"cell_type":"markdown","source":"## Template Classes and Inheritance","metadata":{},"id":"b10bda50-c663-44af-b7e1-5133eaa0a4bc"},{"cell_type":"markdown","source":"Template classes can form a part of an inheritance relationship. They can serve either as base or as derived class. However, some special aspects need to be taken into account in this situation.","metadata":{},"id":"6db3e7f6-d274-4734-aa2d-c9c9b098496f"},{"cell_type":"markdown","source":"### Example 1: Templatisation of a derived class","metadata":{},"id":"556fdcd3-9407-43b8-b708-8c46dcd4e1c5"},{"cell_type":"code","source":"%%file inherit1.cpp\n\n#include <type_traits>\n#include <iostream>\n\nclass Parent{};\n\ntemplate< typename T >\nclass Child : public Parent {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n// #define FAIL\n#ifdef FAIL\n std::cout << \"Child is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child >::value\n << std::endl;\n#endif\n\n std::cout << \"Child< int > is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child< int > >::value\n << std::endl;\n\n std::cout << \"Child< float > is offspring of Parent? That's \"\n << std::is_base_of< Parent, Child< float > >::value\n << std::endl;\n}","metadata":{"trusted":true},"execution_count":9,"outputs":[{"name":"stdout","text":"Overwriting inherit1.cpp\n","output_type":"stream"}],"id":"f5e1615d-911f-4ce4-bdd5-475674330706"},{"cell_type":"code","source":"!g++ inherit1.cpp","metadata":{"trusted":true},"execution_count":10,"outputs":[],"id":"eaab3059-85fd-4200-aee5-8be0fd5c5639"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"Child< int > is offspring of Parent? That's true\nChild< float > is offspring of Parent? That's true\n","output_type":"stream"}],"id":"b33feb73-dc23-4f0a-ba83-f9f80ae8c28f"},{"cell_type":"markdown","source":"Note that each instance of the `Child` template class is a child of the `Parent` base class.","metadata":{},"id":"5d3dc301-5e44-4114-bb65-2fb269af332e"},{"cell_type":"markdown","source":"### Example 2: Deriving from a Templated Base Class","metadata":{},"id":"26d9b143-8f9c-45d2-8fc2-fbe27cdec514"},{"cell_type":"code","source":"%%file inherit2.cpp\n\n#include <iostream>\n\ntemplate< typename T >\nclass Parent{};\n\nclass Child : public Parent< double > {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n std::cout << \"Child is offspring of Parent< double >? That's \"\n << std::is_base_of< Parent< double >, Child >::value\n << std::endl;\n\n std::cout << \"Child is offspring of Parent< int >? That's \"\n << std::is_base_of< Parent< int >, Child >::value\n << std::endl;\n\n}","metadata":{"trusted":true},"execution_count":12,"outputs":[{"name":"stdout","text":"Writing inherit2.cpp\n","output_type":"stream"}],"id":"1d52c9a9-3be5-45bf-b40b-8e7eaf178973"},{"cell_type":"code","source":"!g++ inherit2.cpp; ./a.out","metadata":{"trusted":true},"execution_count":13,"outputs":[{"name":"stdout","text":"Child is offspring of Parent< double >? That's true\nChild is offspring of Parent< int >? That's false\n","output_type":"stream"}],"id":"6afe5ee3-0b5b-48a5-93e8-2e85de7ac3ee"},{"cell_type":"markdown","source":"Different arguments result in different instances of the base class. `Child` is only a child of the specific instance it inherits from. This is important e.g. in the case of specialisation.","metadata":{},"id":"816805a7-856a-47e0-bb59-eb25ac9227c9"},{"cell_type":"markdown","source":"### Example 3: 'Templated' Hierarchy","metadata":{},"id":"acb18101-539d-4a00-b919-9ddda5428b26"},{"cell_type":"code","source":"%%file inherit3.cpp\n\n#include <iostream>\n\ntemplate< typename T >\nclass Parent{};\n\ntemplate< typename T >\nclass Child : public Parent< T > {};\n\nint main() {\n\n std::cout << std::boolalpha;\n\n std::cout << \"Child< short > is offspring of Parent< short >? That's \"\n << std::is_base_of< Parent< short >, Child< short > >::value\n << std::endl;\n\n std::cout << \"Child< float > is offspring of Parent< short >? That's \"\n << std::is_base_of< Parent< short >, Child< float > >::value\n << std::endl;\n\n}","metadata":{"trusted":true},"execution_count":14,"outputs":[{"name":"stdout","text":"Writing inherit3.cpp\n","output_type":"stream"}],"id":"8ab3caf0-78ce-4ad2-8113-c6943c12dc8f"},{"cell_type":"code","source":"!g++ inherit3.cpp; ./a.out","metadata":{"trusted":true},"execution_count":15,"outputs":[{"name":"stdout","text":"Child< short > is offspring of Parent< short >? That's true\nChild< float > is offspring of Parent< short >? That's false\n","output_type":"stream"}],"id":"a399b900-0cc0-46b2-bdfc-3b992c000e7e"},{"cell_type":"markdown","source":"In this case both the base and the derived class are templated. Only instances with the same argument have an inheritance relationship.","metadata":{},"id":"f9f3271f-53b2-4584-a39d-84efcb87938d"},{"cell_type":"markdown","source":"### Member Functions\nLet us take a look at potential pitfalls of inheritance.\n\nThe following example works without problems. We have a templated base class and inherit from a specific instance of it.","metadata":{},"id":"aaf4a1e1-d3d6-47f0-9d3a-590090bc19ee"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {}\n};\n\nclass Child1 : public Parent< double > {\npublic:\n void phoneParent() { ringMe(); }\n};","metadata":{"trusted":true},"execution_count":16,"outputs":[{"name":"stdout","text":"Writing members.cpp\n","output_type":"stream"}],"id":"5995c963-7dec-4fcd-98a4-d7fb3fe233e7"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{"trusted":true},"execution_count":17,"outputs":[],"id":"dd6f2208-c6a8-40dd-a420-f769f43ac962"},{"cell_type":"markdown","source":"Now let us change the derived class to be templated and inherit from a base class with the same parameter.","metadata":{},"id":"b51cb8a0-6dcc-4095-85b5-57aeef973e03"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {}\n};\n\ntemplate< typename T >\nclass Child2 : public Parent< T > {\npublic:\n void phoneParent() { ringMe(); }\n T doSomething( T& ref );\n};","metadata":{"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":"Overwriting members.cpp\n","output_type":"stream"}],"id":"e544a69e-185b-4006-b655-5ad1280126ac"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{"trusted":true},"execution_count":19,"outputs":[{"name":"stdout","text":"members.cpp: In member function ‘void Child2<T>::phoneParent()’:\nmembers.cpp:11:24: error: there are no arguments to ‘ringMe’ that depend on a template parameter, so a declaration of ‘ringMe’ must be available [-fpermissive]\n void phoneParent() { ringMe(); }\n ^~~~~~\nmembers.cpp:11:24: note: (if you use ‘-fpermissive’, G++ will accept your code, but allowing the use of an undeclared name is deprecated)\n","output_type":"stream"}],"id":"4b019b32-ba1a-4b0f-bfb5-49ab3274ddff"},{"cell_type":"markdown","source":"Hmmmm ... what goes wrong here?","metadata":{},"id":"e4f59d30-c5f0-4f29-8035-f2eee3e65db2"},{"cell_type":"markdown","source":"The problem is a result of <a href=\"https://stackoverflow.com/questions/7767626/two-phase-lookup-explanation-needed\">Two-phase Lookup</a>. The latter describes the fact that the compiler examines a template at least twice:\n\n1. The first time (S1), when it checks the syntactical correctness of the template definition and resolves non-dependent names (see below).\n1. A second time (S2), when it attempts to instantiate the template for a given set of template arguments. (To ensure that everything, e.g. a function call, works for the given specific arguments)\n\nOur problem results from the fact that `ringMe()` is a **non-dependent name**, i.e. it does not depend on the template parameter `T`. The compiler needs to resolve a non-dependend name in compile stage (S1). However, this implies that it will not look for `ringMe()` in the `Parent` base class. Since there is neither a declaration of`ringMe()` in the scope of the derived class, nor as a free-function, compilation fails.\n\nThe member function `doSomething`, on the other hand, is a **dependent name**. The latter will be resolved only in stage (S2). Then the compiler also knows from which instance of `Parent` our child class actually derives.","metadata":{},"id":"3eaa22dd-5f89-4a49-830c-d0f332a7d6ac"},{"cell_type":"markdown","source":"How can we still call the base class member function in the derived class? There are multiple ways to solve that conundrum:","metadata":{},"id":"c8601795-7c5b-4eaa-9fbe-c8e280b80034"},{"cell_type":"code","source":"%%file members.cpp\n\ntemplate< typename T >\nclass Parent {\npublic:\n void ringMe() {\n }\n};\n\ntemplate< typename T >\nclass Child3 : public Parent< T > {\npublic:\n\n void phoneParent1() {\n this->ringMe(); // (a)\n }\n\n void phoneParent2() {\n Parent< T >::ringMe(); // (b)\n }\n\n using Parent< T >::ringMe; // (c)\n void phoneParent3() {\n ringMe();\n }\n\n};","metadata":{"trusted":true},"execution_count":20,"outputs":[{"name":"stdout","text":"Overwriting members.cpp\n","output_type":"stream"}],"id":"78992fb6-52ca-4e7b-8c73-0d9812664222"},{"cell_type":"code","source":"!g++ -Wall -Wextra -c members.cpp","metadata":{"trusted":true},"execution_count":21,"outputs":[],"id":"7c113d13-b93f-476e-a7bd-7e363d327109"},{"cell_type":"markdown","source":"* In variant (a) we make the name dependent, by telling the compiler that `ringMe()` is a member function of the current template class (inherited in our case from the base class).\n* In variant (b) we make it explicit, that the function is to be found in the scope of the templated base class, by *fully qualifying* it. This would break a virtual dispatch, though.\n* Variant (c) uses an 'import' into the current scope.","metadata":{},"id":"19274a1c-bd34-438a-b602-62455b94a0e6"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"225919ae-e243-4fde-b193-647efcb382f5"}]} \ No newline at end of file -- GitLab