diff --git a/images/Topics_in_Templates.png b/images/Topics_in_Templates.png new file mode 100644 index 0000000000000000000000000000000000000000..d8946d911a6d06e003acaf38dcd3363149289835 Binary files /dev/null and b/images/Topics_in_Templates.png differ diff --git a/images/cppInsights02.png b/images/cppInsights02.png new file mode 100644 index 0000000000000000000000000000000000000000..722885bc07ab8a6ab9364bbbcdf44ae0da2afc35 Binary files /dev/null and b/images/cppInsights02.png differ diff --git a/notebooks/13_Assorted_Examples.ipynb b/notebooks/13_Assorted_Examples.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9ef93354ac80acd293baf30c5f870e613d2a81d2 --- /dev/null +++ b/notebooks/13_Assorted_Examples.ipynb @@ -0,0 +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":"# Assorted other Examples","metadata":{},"id":"9e611ffc-67dc-4bf7-ad80-747680965827"},{"cell_type":"markdown","source":"Templates form one of the main sublanguages of C++ (Scott Meyers, \"Effective C++\", 2009).\nWe have, by far, not covered all aspects of this. The following diagram list the main areas Rainer Grimm intends to deal with in his current Templates part of\n<a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++</a>:\n\n<img src=\"../images/Topics_in_Templates.png\" width=\"90%\">\n\nTopics marked in yellow, are the ones we already talked about. Those in light red and blue are ones where either some important aspects are still missing, or that we have not covered, yet.\n\nThe plan for this notebook is to take a brief look at these.","metadata":{},"id":"1bb13e1a-64a8-4a7b-9ac9-08189ed15574"},{"cell_type":"markdown","source":"## Template Metaprogramming\n\nQuoting from Wikipedia:\n> <a href=\"https://en.wikipedia.org/wiki/Metaprogramming\">**Metaprogramming**</a> is a programming technique in which computer programs have the ability to treat other programs as their data. It means that a program can be designed to read, generate, analyze or transform other programs, and even modify itself while running.\n\n> <a href=\"https://en.wikipedia.org/wiki/Template_metaprogramming\">Template Metaprogramming</a> is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates can include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time polymorphism.\n\n\n\nExamine the example below taken from Jürgen Lemke, \"C++-Metaprogrammierung\", Springer, 2016.","metadata":{},"id":"d3377acf-83a4-480d-bee6-4e0520f4d476"},{"cell_type":"code","source":"%%file factorial.cpp\n\n#include <iostream>\n\n// general struct\ntemplate <unsigned int i>\nstruct TFactorial {\n enum { eValue = i * TFactorial<i-1>::eValue };\n};\n\n\n// struct for base case\ntemplate <>\nstruct TFactorial<0> {\n enum { eValue = 1 };\n};\n\n\nint main( void ) {\n\n std::cout << \"factorial of 5 is \"\n << TFactorial<5>::eValue\n << std::endl;\n\n return 0;\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"1584a4ef-cafe-44e5-8818-016f3d5e2003"},{"cell_type":"code","source":"!g++ factorial.cpp; ./a.out","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"ff10a24d-af46-4662-8fe1-4ed814778dbc"},{"cell_type":"markdown","source":"The catch here is that the complete computation of 5! is actually performed by the compiler. When we run the executable only the result is printed to the screen. With enough effort we could also make that a part of the compilation step.","metadata":{},"id":"3e8bbe24"},{"cell_type":"markdown","source":"## Template Template Parameters\n\nSo far our template parameters were either\n- type parameters (standard datatypes, classes, ...)\n- non-type parameters (e.g. integer types)\n\nHowever, we can also use class templates as template parameters. These are refered to as\n**template template parameters**.\n\nBefore we examine the details of this. Let us start with an example where we might want to use this.\nAssume that we work with finite element functions and have the following template classes for two standard FE spaces: ","metadata":{},"id":"ff86bb78"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate< typename ValueType >\nclass P1Function {\npublic:\n void info() const {\n std::cout << \"Finite Element Function from P1 Lagrange Space\"\n << \" (ValueType = \" << typeid( ValueType ).name()\n << \")\" << std::endl;\n }\n};\n\ntemplate< typename ValueType >\nclass P2Function {\npublic:\n void info() const {\n std::cout << \"Finite Element Function from P2 Lagrange Space\"\n << \" (ValueType = \" << typeid( ValueType ).name()\n << \")\" << std::endl;\n }\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"c4fae9a9"},{"cell_type":"markdown","source":"The `ValueType` parameter will allow us to instantiate FE functions using different datatypes for their degrees of freedom.\n- We might e.g. use double or float in mixed precision programming.\n- Use `unsigned long int` for enumerating the DoFs when assembling a matrix-vector representation of the FE problem.\n\nNow assume we want to write an operator class representing an operator that maps an FE function onto another FE function from the same space. We want the operator to be a template class and could use our standard approach for this:","metadata":{},"id":"63cd43d6"},{"cell_type":"code","source":"template< typename FunctionType >\nclass OperatorV1 {\npublic:\n void apply( const FunctionType& src, FunctionType& dst ) {\n std::cout << \"src: \";\n src.info();\n std::cout << \"dst: \";\n dst.info();\n }\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"e8b9fb8a"},{"cell_type":"code","source":"int main() {\n P2Function< double > src, dst;\n OperatorV1< P2Function< double > > laplacian;\n std::cout << \"-> applying laplacian\" << std::endl;\n laplacian.apply( src, dst );\n \n OperatorV1< std::string > lazyFail; // remember: implicit instantiation is lazy\n}\n \nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"0483e979"},{"cell_type":"markdown","source":"The approach works, of course. However, it has some properties that might not be optimal in all settings:\n1. We have note expressed that our operator class is intended to work with certain template arguments, only. ($\\rightarrow$ see lazyFail above and *Concepts* in C++20).\n1. There is no direct way to derive from the argument provided for the `FunctionType` template parameter the fact that it is an instance of template class `P2Function` (at least in C++17 and to the best of my knowledge :-). What if we need to generate in the operator an auxillary function of another datatype, e.g. `P2Function<int>`?\n\nAn approach that resolves these issues is to use a **template template parameter**:","metadata":{},"id":"60732d5b-c097-4fcb-9e2b-6debac9e6d34"},{"cell_type":"code","source":"template< template < typename > class func_t >\nclass OperatorV2 {\npublic:\n OperatorV2() {\n func_t< double > aux1;\n func_t< int > aux2;\n std::cout << \"aux1: \"; aux1.info();\n std::cout << \"aux2: \"; aux2.info();\n }\n\n template< typename ValueType >\n void apply( const func_t< ValueType >& src, func_t< ValueType >& dst )\n {\n std::cout << \"src: \";\n src.info();\n std::cout << \"dst: \";\n dst.info();\n }\n\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"f9b9cb47"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"\\n-> instantiating oper\" << std::endl;\n OperatorV2< P1Function > oper;\n\n std::cout << \"\\n-> applying oper\" << std::endl;\n P1Function< float > srcP1, dstP1;\n oper.apply( srcP1, dstP1 ); // instantiates OperatorV2::apply for float\n}\n\nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"1b69d421"},{"cell_type":"markdown","source":"Note that the example also demonstrates the use of a **template member function**, `OperatorV2::apply()`.\n\n---\nIf we are only interested in retaining the information on the template class, we can also use an approach with two template parameters:","metadata":{},"id":"053c49a5"},{"cell_type":"code","source":"template< template < typename > class func_t, typename value_t >\nclass OperatorV3 {\npublic:\n OperatorV3() {\n func_t< value_t > aux1;\n func_t< int > aux2;\n std::cout << \"aux1: \"; aux1.info();\n std::cout << \"aux2: \"; aux2.info();\n }\n\n void apply( const func_t< value_t >& src, func_t< value_t >& dst )\n {\n std::cout << \"src: \";\n src.info();\n std::cout << \"dst: \";\n dst.info();\n }\n\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"7f98cebb"},{"cell_type":"code","source":"int main() {\n\n std::cout << \"\\n-> instantiating mass oper\" << std::endl;\n // OperatorV3< P1Function< float > > mass;\n OperatorV3< P1Function, float > mass;\n \n std::cout << \"\\n-> applying mass oper\" << std::endl;\n P1Function< float > src, dst;\n mass.apply( src, dst );\n}\n\nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"dd6a1f5d"},{"cell_type":"markdown","source":"## Constexpr If\n\n`if constexpr` was introduced in C++17 and consititutes a form of *conditional compilation* in the context of templates. Let us directply plunge into a first example:","metadata":{},"id":"80018e40-0bbe-4949-b352-f1467e829df9"},{"cell_type":"code","source":"%%file demo.cpp\n\n#include <iostream>\n\nclass A {\npublic:\n void sayHello() { std::cout << \"Class A says 'Hello'\" << std::endl;\n }\n};\n\nclass B {\npublic:\n void gruss() { std::cout << \"Guten Morgen!\" << std::endl;\n }\n};\n\ntemplate< typename T >\nvoid checkMessage( T& obj ) {\n if ( std::is_same_v< A, T > ) {\n obj.sayHello();\n }\n else if( std::is_same_v< B, T > ) {\n obj.gruss();\n }\n}\n\nint main() {\n A objTypeA;\n checkMessage( objTypeA );\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"4d05e6d4-b370-40a6-a5ad-334bbe8ed3be"},{"cell_type":"code","source":"!g++ -std=c++17 demo.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"00c57191-2b46-4181-ad72-4595ac882d03"},{"cell_type":"markdown","source":"The *constexpr if* allows us to resolve this issue:","metadata":{},"id":"451b033c-a240-4b1d-b94b-5b0d7dc75d90"},{"cell_type":"code","source":"%%file demo.cpp\n\n#include <iostream>\n\nclass A {\npublic:\n void sayHello() { std::cout << \"Class A says 'Hello'\" << std::endl;\n }\n};\n\nclass B {\npublic:\n void gruss() { std::cout << \"Guten Morgen!\" << std::endl;\n }\n};\n\ntemplate< typename T >\nvoid checkMessage( T& obj ) {\n if constexpr( std::is_same_v< A, T > ) { // <- here is the important change!\n obj.sayHello();\n }\n else if( std::is_same_v< B, T > ) {\n obj.gruss();\n // double a; a->foo();\n }\n}\n\nint main() {\n A objTypeA;\n checkMessage( objTypeA );\n\n B objTypeB;\n checkMessage( objTypeB );\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"6be129fa"},{"cell_type":"code","source":"!g++ -std=c++17 demo.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"1ac9c0ba"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"083cc1e4"},{"cell_type":"markdown","source":"What is the difference?\n\nThe condition in an *constexpr if* must be in general be a <a href=\"https://en.cppreference.com/w/cpp/language/constant_expression#Converted_constant_expression\">contextually converted constant expression of type bool</a>.\n\nIn our template example it is something the compiler can check at instantiation! In this case the branch for which condition evaluates to *true* is instantiated, the other not! This is what makes the difference.\n\nNote that the *false* branch, while not being instantiated, must still be syntactically correct. Remember the **two-phase lookup** we discussed. Not being instantiated means the second phase is skipped, but the code still needs to pass the first phase!\n\nThus, this is not equivalent to conditional compilation with preprocessing directives. As can be seen from the following example from cppreference.com:","metadata":{},"id":"c99ff76a"},{"cell_type":"code","source":"void f() {\n if constexpr(false) {\n int i = 0;\n int *p = i; // Error even though in discarded statement\n }\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"ed62c962"},{"cell_type":"markdown","source":"---\nA maybe more realistic example for the use of *constexpr if* is the following from the <a href=\"https://blog.tartanllama.xyz/if-constexpr/\">blog</a> by Sy Brand. Note that this also features automatic return type deduction:","metadata":{},"id":"bd46e337"},{"cell_type":"code","source":"#include <type_traits>\n\ntemplate <typename T>\nauto get_value(T t) {\n if constexpr (std::is_pointer_v<T>)\n return *t;\n else\n return t;\n}","metadata":{},"execution_count":null,"outputs":[],"id":"c273e25c-652a-461d-b351-3123afccbc11"},{"cell_type":"markdown","source":"Now let us test-drive this:","metadata":{},"id":"3c1ba823-1b7e-4d7e-be29-59b0c1782579"},{"cell_type":"code","source":"#include <iostream>\n\nint main() {\n\n int a = 2;\n int* p = &a;\n\n std::cout << \"direct access ..... a = \" << get_value( a ) << std::endl;\n std::cout << \"indirect access ... a = \" << get_value( p ) << std::endl;\n\n}\n\nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"df64d78d-eecb-408d-a9db-1b1a72716de2"},{"cell_type":"markdown","source":"When we inspect the instantiations with <a href=\"https://cppinsights.io/\">cppinsights</a> we see that indeed only the valid branch gets instantiated:\n<center><img src=\"../images/cppInsights02.png\" width=\"70%\"></center>","metadata":{},"id":"25c99ad6"},{"cell_type":"markdown","source":"The *constexpr if* allows to simplify templates by reducing the need for specialisations, like in the following example using template meta-programming:","metadata":{},"id":"396d5d0e"},{"cell_type":"code","source":"%%file power.cpp\n\n#define CONST_EXPR\n\n#ifndef CONST_EXPR\n\ntemplate< int base, unsigned int exp >\nstruct Power {\n static int value() {\n return base * Power< base, exp - 1u >::value();\n }\n};\n\ntemplate< int base >\nstruct Power< base, 0 > {\n static int value() {\n return 1;\n }\n};\n\n#else\n\ntemplate< int base, unsigned int exp >\nstruct Power {\n static int value() {\n if constexpr( exp > 0 ) {\n return base * Power< base, exp - 1u >::value();\n }\n else {\n return 1;\n }\n }\n};\n\n#endif\n\n#include <iostream>\n\nint main() {\n std::cout << \"5^3 = \" << Power<5,3>::value() << std::endl;\n}","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[],"id":"4f01ee73"},{"cell_type":"code","source":"!g++ -std=c++17 power.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"8a4bb240"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"39268b6a"},{"cell_type":"markdown","source":"## Dependent Names Revisited\nWe had encountered the concept of *non-dependent* and <a href=\"https://en.cppreference.com/w/cpp/language/dependent_name\">*dependent names*</a> in notebook # 12, when we looked at inheritance of template classes and their member functions.\n\n* `typename`\n* `template`\n\nAssume we have the following template class:","metadata":{},"id":"b52ae491"},{"cell_type":"code","source":"template< typename T >\nclass A {\npublic:\n using ptrType = T*;\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"8995872a"},{"cell_type":"markdown","source":"Now we write a template function that internally needs to generate a pointer ","metadata":{},"id":"60809792"},{"cell_type":"code","source":"template< typename T >\nvoid doSomething( T& obj ) {\n A<T>::ptrType valPtr;\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"f19c19b9"},{"cell_type":"markdown","source":"Hmm, what is the problem here? Let's try something else:","metadata":{},"id":"fef65d8e"},{"cell_type":"code","source":"template< typename T >\nclass foo {\n using vType = T::valueType;\n};","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"975025b2"},{"cell_type":"markdown","source":"In both cases we use a name that is dependent on the template parameter `T`\n* `A<T>::ptrType`\n* `T::valueType`\n\nThe full details of this are tricky, but basically we need to help the compiler to understand that we are using a type name, by using the `typename` keyword.\n\nFrom cppreference.com:\n> In a declaration or a definition of a template, including alias template, a name that is not a member of the current instantiation and is dependent on a template parameter is not considered to be a type unless the keyword typename is used or unless it was already established as a type name, e.g. with a typedef declaration or by being used to name a base class.\n\nThat explains the error messages from the first example. `A<T>::ptrType` is not seen as a type name, but treated as a member of the template class `A<T>`.\n\nIndeed, adding the `typename` keyword fixes the problem:","metadata":{},"id":"fc965154"},{"cell_type":"code","source":"template< typename T >\nvoid doSomething( T& obj ) {\n typename A<T>::ptrType valPtr;\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"afb191b0"},{"cell_type":"markdown","source":"A similar ambiguity problem can occur with template names:\n\n> Similarly, in a template definition, a dependent name that is not a member of the current instantiation is not considered to be a template name unless the disambiguation keyword template is used or unless it was already established as a template name.\n\nAssume we have class with a templated member function\n```c++\nclass B {\npublic:\n template< typename T >\n void run() {};\n};\n```\nNow we write a template function that internally generates an object, its type being the template parameter `T`, and calls its templated `run()`member function.","metadata":{},"id":"12c0fa43"},{"cell_type":"code","source":"template< typename T >\nvoid func() {\n T obj;\n obj.run< T >();\n}","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[],"id":"695e3775"},{"cell_type":"code","source":"template< typename T >\nvoid func() {\n T obj;\n obj.template run< T >();\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"da772d07"},{"cell_type":"markdown","source":"## CRTP: Curiously Recurring Template Pattern","metadata":{},"id":"bb14f831"},{"cell_type":"markdown","source":"The curiously recurring template patterns describes a technique, where a class B inherits from a template class A, using itself as template argument:\n\n```c++\ntemplate< typename T >\nclass A {};\n\nclass B : public A< B > {};\n```\nA nice list of usage scenarios and examples for CRTP can be found on the webpages of <a href=\"http://www.vishalchovatiya.com/crtp-c-examples/\">Vishal Chovatiya</a>. The first of these demonstrates static polymorphism:","metadata":{},"id":"0af04dfc"},{"cell_type":"code","source":"%%file crtp.cpp\n\n#include <iostream>\n\ntemplate<typename specific_animal>\nstruct animal {\n void who() { static_cast<specific_animal*>(this)->who(); }\n};\n\nstruct dog : animal<dog> {\n void who() { std::cout << \"dog\" << std::endl; }\n};\n\nstruct cat : animal<cat> {\n void who() { std::cout << \"cat\" << std::endl; }\n};\n\ntemplate<typename specific_animal>\nvoid who_am_i(animal<specific_animal> &animal) {\n animal.who();\n}\n\nint main( void ) {\n\n cat c;\n who_am_i(c); // prints `cat`\n\n dog d;\n who_am_i(d); // prints `dog`\n\n}\n","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"fffce178"},{"cell_type":"code","source":"!g++ crtp.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"1e32d10d"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"da65a685"},{"cell_type":"markdown","source":"The catch here is that the `animal` class delegates the actual implementation of the `who()` member function to its children. However, in constrast to dynamic polymorphism with virtual functions this is handled at compile-time and does not induces a run-time penalty.","metadata":{},"id":"3d5e5fae"},{"cell_type":"markdown","source":"The final object counter example is taken from <a href=\"https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Object_counter\">Wikipedia</a>: ","metadata":{},"id":"34f3bf32"},{"cell_type":"code","source":"#include <iostream>\n\ntemplate <typename T>\nstruct counter\n{\n static inline int objects_created = 0;\n static inline int objects_alive = 0;\n\n counter()\n {\n ++objects_created;\n ++objects_alive;\n }\n \n counter(const counter&)\n {\n ++objects_created;\n ++objects_alive;\n }\nprotected:\n ~counter() // objects should never be removed through pointers of this type\n {\n --objects_alive;\n }\n};\n\nclass X : counter<X>\n{\n // ...\n};\n\nclass Y : counter<Y>\n{\n // ...\n};\n\n\nint main() {\n\n Y objY;\n for( int k = 0; k < 2; ++k ) {\n X* ptr = new X;\n Y v;\n }\n\n std::cout << \"X created: \" << counter<X>::objects_created << std::endl;\n std::cout << \"X alive: \" << counter<X>::objects_alive << std::endl;\n\n std::cout << \"Y created: \" << counter<Y>::objects_created << std::endl;\n std::cout << \"Y alive: \" << counter<Y>::objects_alive << std::endl;\n}","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"fcf8be3c"},{"cell_type":"markdown","source":"What will this print?","metadata":{},"id":"f3309e3c"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"d761dd85"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"fc82d71b"}]} \ No newline at end of file