diff --git a/notebooks/12_Template_Basics_cont.ipynb b/notebooks/12_Template_Basics_cont.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2bbb3285a4d914cc88fad89b74717afa260fbce3 --- /dev/null +++ b/notebooks/12_Template_Basics_cont.ipynb @@ -0,0 +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