diff --git a/images/cppInsights01.png b/images/cppInsights01.png new file mode 100644 index 0000000000000000000000000000000000000000..a954b0694b144de8305eea04541d29ecf9f1eaf1 Binary files /dev/null and b/images/cppInsights01.png differ diff --git a/notebooks/11_Template_Basics.ipynb b/notebooks/11_Template_Basics.ipynb index 1f9af8bb187b3b541922fb94b9267cd9a97fa991..95d53c8b1c1fae35ae7735b91516775884f7f858 100644 --- a/notebooks/11_Template_Basics.ipynb +++ b/notebooks/11_Template_Basics.ipynb @@ -1 +1,1153 @@ -{"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":"# 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":{"trusted":true},"execution_count":null,"outputs":[],"id":"d4a7139f"},{"cell_type":"code","source":"// Version for double\ndouble getMagnitude( double input ) {\n return input > 0.0 ? input : -input;\n}","metadata":{"trusted":true},"execution_count":null,"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":{"trusted":true},"execution_count":null,"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":{"trusted":true},"execution_count":1,"outputs":[{"name":"stdout","text":"Overwriting getMag.cpp\n","output_type":"stream"}],"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":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","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","output_type":"stream"}],"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":{"trusted":true},"execution_count":3,"outputs":[{"name":"stdout","text":"Overwriting getMag.cpp\n","output_type":"stream"}],"id":"b1d7de95"},{"cell_type":"code","source":"!g++ -Wall -Wextra getMag.cpp","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"b85f934b"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":5,"outputs":[{"name":"stdout","text":"|-2| = 2\n|3.5| = 3.5\n|12.34| = 12.34\n","output_type":"stream"}],"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":"---\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":null,"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":null,"outputs":[],"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 closer 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":null,"outputs":[],"id":"8f2ab60c"},{"cell_type":"code","source":"!g++ -save-temps -c withTemplates.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"96551727-c1b0-4b97-9bff-11e9eeac731e"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":null,"outputs":[],"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.","metadata":{},"id":"d63382c0-e766-422b-ba97-7824de507c3a"},{"cell_type":"code","source":"!cat withTemplates.ii","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"9c620720-b4cc-46a0-bde0-57452bcd3a0b"},{"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":null,"outputs":[],"id":"38aa85e7-83d9-4ae0-ac05-80cf187ee9f3"},{"cell_type":"code","source":"!g++ -save-temps -c withTemplates.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"76bfe507-bc01-457a-aeb5-38e2db572cac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":null,"outputs":[],"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":null,"outputs":[],"id":"a854c3ce-1865-4ec1-b092-87be2a50d13f"},{"cell_type":"code","source":"%%file withTemplates.hpp\n\ntemplate< typename T > T getMagnitude( T value );","metadata":{"trusted":true},"execution_count":null,"outputs":[],"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":null,"outputs":[],"id":"c96bbc17-3771-453b-9544-eae96dd936f0"},{"cell_type":"code","source":"!g++ driver.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"0cea5aa2-240b-493c-8afc-c560b0dff82c"},{"cell_type":"markdown","source":"Okay, that is not different from the usual. Let us also compile the source code file:","metadata":{},"id":"976e6ef4-ce58-4a4f-acf9-bff9d122923d"},{"cell_type":"code","source":"!g++ driver.cpp withTemplates.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"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":null,"outputs":[],"id":"846d3074-53c4-4dfe-94af-43b82f49d0a8"},{"cell_type":"code","source":"!nm -C driver.o","metadata":{"trusted":true},"execution_count":null,"outputs":[],"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.\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":null,"outputs":[],"id":"15c7f98e-33fd-4bca-8293-5a19352c43ac"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":null,"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 with out 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":null,"outputs":[],"id":"e0aa1181-d4b2-4fc1-95fe-ead0f86b2d5b"},{"cell_type":"code","source":"!g++ -save-temps driver.cpp withTemplates.cpp","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"e86d0de4-3e7e-48d1-8b2f-3a2b29e44842"},{"cell_type":"code","source":"!nm -C withTemplates.o","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"380b4117-4a81-45dc-a578-88f233d8726e"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"68c3df86-8d83-4b24-bb13-8bb90e170eb2"}]} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "id": "741a2490", + "metadata": {}, + "source": [ + "# Template Programming: Basics" + ] + }, + { + "cell_type": "markdown", + "id": "a61d218d", + "metadata": { + "tags": [] + }, + "source": [ + "## Motivation\n", + "\n", + "In our first notebook **01_Overloading** we started with implementing two free-functions that returned the magnitude of their argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d4a7139f", + "metadata": {}, + "outputs": [], + "source": [ + "// Version for int\n", + "int getMagnitude( int input ) {\n", + " return input > 0 ? input : -input;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cfbe1b82", + "metadata": {}, + "outputs": [], + "source": [ + "// Version for double\n", + "double getMagnitude( double input ) {\n", + " return input > 0.0 ? input : -input;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "6f937e9d", + "metadata": {}, + "source": [ + "Comparison of the code shows that there are three differences between the two functions:\n", + "1. type of argument\n", + "1. type of return value\n", + "1. type of literal used\n", + "\n", + "The last one can be eliminated by performing a static cast." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0225265d", + "metadata": {}, + "outputs": [], + "source": [ + "// Version for double\n", + "double getMagnitude( double input ) {\n", + " return input > static_cast< double >( 0 ) ? input : -input;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "13e637a9", + "metadata": {}, + "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", + "\n", + "This 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:\n", + "TYPE getMagnitude( TYPE input ) {\n", + " return input > static_cast< TYPE >( 0 ) ? input : -input;\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "3013b562", + "metadata": {}, + "source": [ + "---\n", + "**Alternatives?**\n", + "\n", + "It 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", + "\n", + "Fortunately there are ways to avoid this. The two prominent ones applicable in our situation are:\n", + "1. (Automatic) Code Generation\n", + "1. Generic Programming" + ] + }, + { + "cell_type": "markdown", + "id": "6dc7ea86", + "metadata": { + "tags": [] + }, + "source": [ + "### (Automatic) Code Generation\n", + "\n", + "Here the idea is to let the computer generate the source code that the compiler sees itself following our instructions.\n", + "\n", + "For 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", + "\n", + "Often such generators are tuned to a specific field of problems and involve a so called\n", + "DSL (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", + "\n", + "Automatic 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 ...)" + ] + }, + { + "cell_type": "markdown", + "id": "3191c55f", + "metadata": {}, + "source": [ + "### 'Inline' Code Generation\n", + "In 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", + "\n", + "We are going to take a look at this as an example, before proceeding to templates." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "988d30ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing getMag.cpp\n" + ] + } + ], + "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\n", + "GETMAG( int )\n", + "GETMAG( double )\n", + "GETMAG( float )" + ] + }, + { + "cell_type": "markdown", + "id": "625b1b11", + "metadata": {}, + "source": [ + "Now let us check how the result of running the preprocessor on that source code looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8baeee45", + "metadata": {}, + "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\"\n", + "int getMagnitude( int value ) { return value > static_cast< int >( 0 ) ? value : -value; }\n", + "double getMagnitude( double value ) { return value > static_cast< double >( 0 ) ? value : -value; }\n", + "float getMagnitude( float value ) { return value > static_cast< float >( 0 ) ? value : -value; }\n" + ] + } + ], + "source": [ + "!cpp getMag.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "9ea5d77e", + "metadata": {}, + "source": [ + "---\n", + "Preprocessing is an integral part of compilation of a C++ program (think of the `#include` directive) so this integrates seamlessly:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b1d7de95", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting getMag.cpp\n" + ] + } + ], + "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\n", + "GETMAG( signed char )\n", + "GETMAG( short )\n", + "GETMAG( int )\n", + "GETMAG( long )\n", + "GETMAG( float )\n", + "GETMAG( double )\n", + " \n", + "#include <iostream>\n", + "int 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", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b85f934b", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra getMag.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "00684f47", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "|-2| = 2\n", + "|3.5| = 3.5\n", + "|12.34| = 12.34\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "6631c752", + "metadata": {}, + "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", + "\n", + "Writing longer functions will become irksome, also, due to the line continuation stuff. *(and no syntax high-lighting or indentation ... by editors).*\n", + "\n", + "Also 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." + ] + }, + { + "cell_type": "markdown", + "id": "dda3bd44", + "metadata": {}, + "source": [ + "### Generic Programming\n", + "The 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", + "\n", + "From <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", + "\n", + "In C++ the approach to support generic programming is to use **templates**." + ] + }, + { + "cell_type": "markdown", + "id": "c9b5ee37", + "metadata": {}, + "source": [ + "## C++ Template Basics\n", + "\n", + "So how would we takle our problem with templates?\n", + "\n", + "Instead of multiple free-functions we implement a **single templated version** of it:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "90ddfa90", + "metadata": {}, + "outputs": [], + "source": [ + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "e2df86d3", + "metadata": {}, + "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): " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f5be2836", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "|-5| = 5" + ] + } + ], + "source": [ + "#include <iostream>\n", + "short s = -5;\n", + "std::cout << \"|\" << s << \"| = \" << getMagnitude( s );" + ] + }, + { + "cell_type": "markdown", + "id": "740ee0bd", + "metadata": {}, + "source": [ + "---\n", + "A closer look at the **function template**:\n", + "\n", + "```c++\n", + "template< typename T >\n", + "T 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" + ] + }, + { + "cell_type": "markdown", + "id": "d6754844-ef2d-496e-adab-1367a463c19f", + "metadata": {}, + "source": [ + "---\n", + "Now let's take a look at the **instantiation**:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "8f2ab60c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting withTemplates.cpp\n" + ] + } + ], + "source": [ + "%%file withTemplates.cpp\n", + "\n", + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}\n", + " \n", + "int 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", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "96551727-c1b0-4b97-9bff-11e9eeac731e", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -save-temps -c withTemplates.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d3e0f1fc-f85a-4adc-a63f-ba7fdea02a2d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0000000000000000 T main\n", + "0000000000000000 W double getMagnitude<double>(double)\n", + "0000000000000000 W float getMagnitude<float>(float)\n", + "0000000000000000 W long getMagnitude<long>(long)\n", + "0000000000000000 W short getMagnitude<short>(short)\n" + ] + } + ], + "source": [ + "!nm -C withTemplates.o" + ] + }, + { + "cell_type": "markdown", + "id": "d63382c0-e766-422b-ba97-7824de507c3a", + "metadata": {}, + "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", + "\n", + "A 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." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "9c620720-b4cc-46a0-bde0-57452bcd3a0b", + "metadata": {}, + "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", + "\n", + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}\n", + "\n", + "int 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" + ] + } + ], + "source": [ + "!cat withTemplates.ii" + ] + }, + { + "cell_type": "markdown", + "id": "5cfc8599", + "metadata": {}, + "source": [ + "We can, however, also **explicitely** request the compiler to instantiate a version for a specific template argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "38aa85e7-83d9-4ae0-ac05-80cf187ee9f3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting withTemplates.cpp\n" + ] + } + ], + "source": [ + "%%file withTemplates.cpp\n", + "\n", + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}\n", + "\n", + "// explicit instantiation\n", + "template float getMagnitude( float );\n", + "\n", + "int main() {\n", + "\n", + " // implicit instantiation\n", + " getMagnitude( -2 );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "76bfe507-bc01-457a-aeb5-38e2db572cac", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -save-temps -c withTemplates.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e707d5e2-6d8d-4ee9-9046-b749d154146b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0000000000000000 T main\n", + "0000000000000000 W float getMagnitude<float>(float)\n", + "0000000000000000 W int getMagnitude<int>(int)\n" + ] + } + ], + "source": [ + "!nm -C withTemplates.o" + ] + }, + { + "cell_type": "markdown", + "id": "76ecc704-ac09-4343-b561-7817373aa1c9", + "metadata": {}, + "source": [ + "When might explicit instantiation be required?\n", + "\n", + "Let us modify our example to have the usual structure of *header file* (.hpp) and *source code file* (.cpp) and a separate *driver file*:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "a854c3ce-1865-4ec1-b092-87be2a50d13f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting withTemplates.cpp\n" + ] + } + ], + "source": [ + "%%file withTemplates.cpp\n", + "\n", + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "b3b433db-8509-43fb-9946-579ffd4c5dfb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting withTemplates.hpp\n" + ] + } + ], + "source": [ + "%%file withTemplates.hpp\n", + "\n", + "// only a prototype\n", + "template< typename T > T getMagnitude( T value );" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "c96bbc17-3771-453b-9544-eae96dd936f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting driver.cpp\n" + ] + } + ], + "source": [ + "%%file driver.cpp\n", + "\n", + "#include \"withTemplates.hpp\"\n", + "int main() {\n", + " getMagnitude( -1.2345f );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "0cea5aa2-240b-493c-8afc-c560b0dff82c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/ccYQKyzG.o: In function `main':\n", + "driver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\n", + "collect2: error: ld returned 1 exit status\n" + ] + } + ], + "source": [ + "!g++ driver.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "976e6ef4-ce58-4a4f-acf9-bff9d122923d", + "metadata": {}, + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "e98425fb-dc85-4e89-9f9b-dc310b1c65bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/ccXICvwu.o: In function `main':\n", + "driver.cpp:(.text+0xd): undefined reference to `float getMagnitude<float>(float)'\n", + "collect2: error: ld returned 1 exit status\n" + ] + } + ], + "source": [ + "!g++ driver.cpp withTemplates.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "0ace12fa-d5a8-48ab-8d9b-250c71847812", + "metadata": {}, + "source": [ + "Still a problem! Why's that?" + ] + }, + { + "cell_type": "markdown", + "id": "bf20a5c9-5d48-405f-861e-afc7d699ff41", + "metadata": {}, + "source": [ + "---\n", + "\n", + "\n", + "*intentionally left blank ;-)*\n", + "\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "1517f452-ef0e-471a-82c0-880e829a51ff", + "metadata": {}, + "source": [ + "When we compile the driver the compiler will recognise that it need a version of `getMagnitude()` for `float`:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "846d3074-53c4-4dfe-94af-43b82f49d0a8", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -c driver.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "7f84b616-97d9-4ce0-a588-7bed26c4b4aa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " U _GLOBAL_OFFSET_TABLE_\n", + "0000000000000000 T main\n", + " U float getMagnitude<float>(float)\n" + ] + } + ], + "source": [ + "!nm -C driver.o" + ] + }, + { + "cell_type": "markdown", + "id": "43c992a6-7dca-4aab-8293-328b0cd8bf76", + "metadata": {}, + "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", + "\n", + "Okay. So now let us compile the source code file and check the contents of the resulting object file:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "15c7f98e-33fd-4bca-8293-5a19352c43ac", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -c withTemplates.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "5f6a4521-affc-4147-a33a-a7fd4f6051ce", + "metadata": {}, + "outputs": [], + "source": [ + "!nm -C withTemplates.o" + ] + }, + { + "cell_type": "markdown", + "id": "b955ee26-e340-44e5-a264-66beda062321", + "metadata": {}, + "source": [ + "Oops. Nothing. Why?\n", + "\n", + "Because in the source code file no template instance is required and the template function itself cannot be compiled without an argument for the datatype!" + ] + }, + { + "cell_type": "markdown", + "id": "6bd45ab9-c3b9-4ec2-b093-1a2f327a0526", + "metadata": {}, + "source": [ + "---\n", + "Splitting declaration and definition of a template function requires us to use explicit instantiation." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e0aa1181-d4b2-4fc1-95fe-ead0f86b2d5b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting withTemplates.cpp\n" + ] + } + ], + "source": [ + "%%file withTemplates.cpp\n", + "\n", + "template< typename T >\n", + "T getMagnitude( T value ) {\n", + " return value > static_cast< T >( 0 ) ? value : -value;\n", + "}\n", + "\n", + "template float getMagnitude( float );" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "e86d0de4-3e7e-48d1-8b2f-3a2b29e44842", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -save-temps driver.cpp withTemplates.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "380b4117-4a81-45dc-a578-88f233d8726e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0000000000000000 W float getMagnitude<float>(float)\n" + ] + } + ], + "source": [ + "!nm -C withTemplates.o" + ] + }, + { + "cell_type": "markdown", + "id": "9368884e", + "metadata": {}, + "source": [ + "### Template Classes\n", + "\n", + "Naturally 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." + ] + }, + { + "cell_type": "markdown", + "id": "f91b42be", + "metadata": {}, + "source": [ + "As an example let us write a small wrapper class for a static array (note that the usefull variant of this would be `std::array` from the STL)." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "5d7941c5", + "metadata": {}, + "outputs": [], + "source": [ + "template< typename T, int N >\n", + "struct StaticArray {\n", + " int getSize() { return N; };\n", + " T data [N];\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "69a7c099", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "StaticArray< double, 3 > shortVec;\n", + "shortVec.getSize()" + ] + }, + { + "cell_type": "markdown", + "id": "db743abf", + "metadata": {}, + "source": [ + "Observations:\n", + "1. 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.\n", + "1. Template parameters are not restricted to type parameters. We can also use **non-type parameters**:\n", + " - integer types (`int`, `short`, `bool`, ...)\n", + " - floating-point types (since C++20)\n", + " - pointers and references\n", + "the argument for instantiation then is a value of that type.\n", + "1. 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.\n", + "1. 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." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "a2978a5c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[1minput_line_13:2:2: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1muse of class template 'StaticArray' requires template arguments\u001b[0m\n", + " StaticArray longVec;\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1minput_line_8:2:8: \u001b[0m\u001b[0;1;30mnote: \u001b[0mtemplate is declared here\u001b[0m\n", + "struct StaticArray {\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m" + ] + }, + { + "ename": "Interpreter Error", + "evalue": "", + "output_type": "error", + "traceback": [ + "Interpreter Error: " + ] + } + ], + "source": [ + "StaticArray longVec;" + ] + }, + { + "cell_type": "markdown", + "id": "a8f52ca7", + "metadata": {}, + "source": [ + "### Lazy vs Eager Instantiation" + ] + }, + { + "cell_type": "markdown", + "id": "9f21c5d4", + "metadata": {}, + "source": [ + "In C++ **implicit instantiation is lazy**. In the case of a template class this means that only member functions that are not called do not get instantiated! \n", + "\n", + "**Explicit instantiation**, on the other hand, is eager.\n", + "\n", + "We can see this from the following (slightly extended) example taken from \n", + "Rainer Grimm's blog <a href=\"https://www.grimm-jaud.de/index.php/blog\">Modernes C++ in der Praxis</a>." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "a06c3b23", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting lazy.cpp\n" + ] + } + ], + "source": [ + "%%file lazy.cpp\n", + "\n", + "#include <cmath>\n", + "#include <string>\n", + "\n", + "template< typename T >\n", + "struct 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", + "\n", + "int 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", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "06c4ffb9", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ lazy.cpp" + ] + }, + { + "cell_type": "markdown", + "id": "c89daa16", + "metadata": {}, + "source": [ + "Let us check this for our previous example, too." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4fd246ab", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "template< typename T, int N >\n", + "struct StaticArray {\n", + " int getSize() { return N; };\n", + " T data[N];\n", + "};\n", + "\n", + "int main() {\n", + "\n", + " StaticArray< float, 5 > shortVec;\n", + " StaticArray< short, 100 > longVec;\n", + " StaticArray< float, 5 > shortVec2;\n", + " std::cout << longVec.getSize();\n", + " \n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "1a01b8b8", + "metadata": {}, + "source": [ + "Questions:\n", + "- Will `getSize()` be instantiated for `StaticArray< float, 5 >`?\n", + "- How many times will `StaticArray< float, 5 >` be instantiated?\n", + "In 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." + ] + }, + { + "cell_type": "markdown", + "id": "ca2aa233", + "metadata": {}, + "source": [ + "<img align=\"left\" src=\"../images/cppInsights01.png\" />" + ] + }, + { + "cell_type": "markdown", + "id": "c9580c48", + "metadata": {}, + "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.)*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da055e27", + "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 +}