From d63f426eacb5dcbd95fc8ff9b03a37db5e1fdc18 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Mon, 13 Dec 2021 17:32:37 +0100 Subject: [PATCH] Adds frist draft of sub-section on lambda expression --- notebooks/08_Functors+Lambdas.ipynb | 638 +++++++++++++++++++++++++++- 1 file changed, 617 insertions(+), 21 deletions(-) diff --git a/notebooks/08_Functors+Lambdas.ipynb b/notebooks/08_Functors+Lambdas.ipynb index f0c89de..9b38def 100644 --- a/notebooks/08_Functors+Lambdas.ipynb +++ b/notebooks/08_Functors+Lambdas.ipynb @@ -8,15 +8,6 @@ "# Functors, Lambdas and std::function" ] }, - { - "cell_type": "markdown", - "id": "43efc656", - "metadata": {}, - "source": [ - " \n", - " " - ] - }, { "cell_type": "markdown", "id": "008aa425", @@ -222,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "856ee747", "metadata": {}, "outputs": [ @@ -278,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "id": "4adb7f0b", "metadata": {}, "outputs": [], @@ -288,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "id": "f9bfde7f", "metadata": {}, "outputs": [ @@ -409,12 +400,12 @@ "metadata": {}, "source": [ "##### **Example 5:**\n", - "Now lets use a functor with a slightly more complicated interface" + "Now let us use a functor with a slightly more complicated interface" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 20, "id": "ff8d558b-da83-46d9-9842-8e0d9d5ba56d", "metadata": {}, "outputs": [], @@ -427,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 21, "id": "b5790b96-bf62-4d6e-ad9c-e56839882728", "metadata": {}, "outputs": [ @@ -437,7 +428,7 @@ "6" ] }, - "execution_count": 16, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -465,7 +456,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 22, "id": "646b302f-cdf5-40ed-98f8-01d98a9b277a", "metadata": {}, "outputs": [], @@ -482,7 +473,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 26, "id": "b56b370a-654f-4665-a86d-1b4a1497a813", "metadata": {}, "outputs": [], @@ -492,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 27, "id": "b267e62f", "metadata": {}, "outputs": [ @@ -519,13 +510,618 @@ "#### Lambdas\n", "Using *function pointers* or *functors* implies some significant coding overhead.\n", "\n", - "C++11 introduced **lambda (functions)**, also known as **closures** as a leight-weight alternative, that can be written inline in the source code." + "C++11 introduced **lambda (functions)**, also known as **closures**, as a leight-weight alternative, that can be written inline in the source code." + ] + }, + { + "cell_type": "markdown", + "id": "41bbe5a3", + "metadata": {}, + "source": [ + "Let's start with an example" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "24ad0ef1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1 4 9 16 25" + ] + } + ], + "source": [ + "#include <vector>\n", + "#include <iostream>\n", + "#include <algorithm>\n", + "\n", + "std::vector< int > numbers{1,2,3,4,5};\n", + "\n", + "for_each( numbers.begin(), numbers.end(), []( int& n ) { n *= n; } );\n", + "\n", + "for( int v: numbers ) std::cout << \" \" << v;" + ] + }, + { + "cell_type": "markdown", + "id": "ff3d112c", + "metadata": {}, + "source": [ + "The expression **`[]( int& n ) { n *= n; }`** is an *anonymous*, so called, lambda, lambda expression, lambda function or closure.\n", + "\n", + "- The `(...)` part specifies the interface.\n", + "- If a lambda function does not return a value we can omit the return type.\n", + "- The `{...}` part is for the body of the function.\n", + "- The `[...]` part allows to *capture* variables from the current scope.\n", + "\n", + "lambda functions do not really provide extra functionality. We could accomplish the same with a free-function or a functor. In this sense they are only syntactic sugar, \"but of the sweetest kind\" (Rainer Grimm)." + ] + }, + { + "cell_type": "markdown", + "id": "5764a9c9", + "metadata": {}, + "source": [ + "##### Captures:\n", + "\n", + "The `[ ]` part of the lambda expression can contain a comma-separated list of stuff we want to *capture*. This allows using it inside the body of the lambda without explicitely passing it through the interface. There are also two *defaults* we can speficy:\n", + "\n", + "| | effect |\n", + "|:-----:|:-------|\n", + "| & | implicitly capture the used automatic variables by reference |\n", + "| : | implicitly capture the used automatic variables by copy |\n", + "| a | capture a from surrounding scope by copy |\n", + "| &a | capture a from surrounding scope by reference |\n", + "\n", + "See e.g. <a href=\"https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture\">cppreference.com</a> for a full list of possibilities." + ] + }, + { + "cell_type": "markdown", + "id": "645373e5", + "metadata": {}, + "source": [ + "We are going to take a look at three examples from <a href=\"https://www.grimm-jaud.de/index.php/modernes-c-in-der-praxis-linux-magazin-a\">Modernes C++ in der Praxis</a> by Rainer Grimm." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3514f84c", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "int main() {\n", + "\n", + " std::cout << std::endl;\n", + "\n", + " std::string copy = \"original\";\n", + " std::string ref = \"original\";\n", + "\n", + " auto lambda = [copy,&ref] { std::cout << copy << \" \" << ref << std::endl; };\n", + "\n", + " lambda();\n", + "\n", + " copy = \"changed\";\n", + " ref = \"changed\";\n", + "\n", + " lambda();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7bca56ac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "original original\n", + "original changed\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "b237f560", + "metadata": {}, + "source": [ + "In line #10 we have our lambda expression.\n", + "- It does not receive arguments through its interface. Thus, we can omit the `( )` part.\n", + "- When we want to re-use a lambda we can store it in a variable. For this we need to use `auto`, asSmalleSmalle.\n", + "- Note the difference in the output between the two invocations of `lambda()`. As we captured `copy` by copy its value inside `lambda()` is not affected by its change in the surrounding scope, while that of `ref` is. The latter was captured by reference." + ] + }, + { + "cell_type": "markdown", + "id": "ac5c13c5", + "metadata": {}, + "source": [ + "***\n", + "Small detour: Coming back to what the type is. Let's try to figure out using `typeid`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f0fd0543", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0 is of type d\n", + "type of a is i\n", + "What is lambda? ZN11__cling_N5415__cling_Un1Qu33EPvE3$_1\n" + ] + } + ], + "source": [ + "std::cout << \"5.0 is of type \" << typeid(5.0).name() << std::endl;\n", + "int a;\n", + "std::cout << \"type of a is \" << typeid(a).name() << std::endl;\n", + "\n", + "\n", + "auto lambda = [] (int a) { std::cout << a << std::endl; };\n", + "std::cout << \"What is lambda? \" << typeid(lambda).name() << std::endl;" + ] + }, + { + "cell_type": "markdown", + "id": "3393bdde", + "metadata": {}, + "source": [ + "Not very helpful, hugh. So maybe let's try something else" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "a32b1635", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting lambda.cpp\n" + ] + } + ], + "source": [ + "%%file lambda.cpp\n", + "\n", + "void test() {\n", + "\n", + "auto lambda = [] (int &a) { a++; };\n", + "\n", + "int b = 1;\n", + "lambda(b); // avoid optimizing out\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "85373b9f", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -c lambda.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "fa038d35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " U _GLOBAL_OFFSET_TABLE_\n", + " U __stack_chk_fail\n", + "000000000000001e T test()\n", + "0000000000000000 t test()::{lambda(int&)#1}::operator()(int&) const\n" + ] + } + ], + "source": [ + "!nm -C lambda.o" + ] + }, + { + "cell_type": "markdown", + "id": "ab79093e", + "metadata": {}, + "source": [ + "Okay, so that really looks very specific. Indeed **\"the type of a closure cannot be named\"** (at least not explicitely), but it can be detected by **`auto`**." + ] + }, + { + "cell_type": "markdown", + "id": "d65d40b3", + "metadata": {}, + "source": [ + "***\n", + "Another example from *Modernes C++ in der Praxis*. Here we implement a join method for strings." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "03a872a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting join.cpp\n" + ] + } + ], + "source": [ + "%%file join.cpp\n", + "\n", + "#include <iostream>\n", + "#include <algorithm>\n", + "#include <string>\n", + "#include <vector>\n", + "\n", + "std::string join( std::vector< std::string >& str, std::string sep = \" \" ) {\n", + "\n", + " std::string joinStr = \"\";\n", + "\n", + " // small mod from original\n", + " // if( not str.size()() ) return joinStr;\n", + " if( str.size() == 0 ) return joinStr;\n", + "\n", + " std::for_each( str.begin(), str.end()-1,\n", + " [ &joinStr, sep ] (std::string v) { joinStr += v + sep; } );\n", + "\n", + " joinStr += str.back(); // back() returns reference to last entry\n", + " return joinStr;\n", + "}\n", + "\n", + "int main() {\n", + "\n", + " std::vector< std::string > myVec;\n", + " std::cout << join( myVec ) << std::endl;\n", + "\n", + " myVec.push_back( \"One\" );\n", + " std::cout << join( myVec ) << std::endl;\n", + "\n", + " myVec.push_back( \"Two\" );\n", + " std::cout << join( myVec ) << std::endl;\n", + "\n", + " myVec.push_back( \"Three\" );\n", + " std::cout << join( myVec, \":\" ) << std::endl;\n", + "\n", + " myVec.push_back( \"Four\" );\n", + " std::cout << join( myVec, \"/\" ) << std::endl;\n", + "\n", + " myVec.push_back( \"Five\" );\n", + " std::cout << join( myVec, \"XXX\" ) << std::endl;\n", + "\n", + " std::cout << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "933de670", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra join.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c485b43f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "One\n", + "One Two\n", + "One:Two:Three\n", + "One/Two/Three/Four\n", + "OneXXXTwoXXXThreeXXXFourXXXFive\n", + "\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "f839546e", + "metadata": {}, + "source": [ + "Final example from Modernes C++ in der Praxis. Here we implement a flexible filter to operate on a vector of random numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "dcaec377", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting filter.cpp\n" + ] + } + ], + "source": [ + "%%file filter.cpp\n", + "\n", + "#include <algorithm>\n", + "#include <functional>\n", + "#include <iostream>\n", + "#include <random>\n", + "\n", + "// This is going to be our filter\n", + "std::function< bool(int) > inRange( int low, int up ) {\n", + " return [low,up] ( int d ) { return d >= low && d <= up; };\n", + "}\n", + "\n", + "\n", + "int main() {\n", + "\n", + " std::cout << std::boolalpha << std::endl;\n", + "\n", + " std::cout << \"4 inRange(5,10): \" << inRange(5,10)(4) << std::endl;\n", + "\n", + " auto filt = inRange(5,10);\n", + "\n", + " std::cout << \"5 inRange(5,10): \" << filt(5) << std::endl;\n", + "\n", + " std::cout << std::endl;\n", + "\n", + " const int NUM = 60;\n", + "\n", + " std::random_device seed;\n", + " std::mt19937 engine( seed() ); // \n", + "\n", + " // distribution\n", + " std::uniform_int_distribution< int > six(1,6);\n", + "\n", + " std::vector< int > dice;\n", + " for( int i = 1; i <= NUM; ++i ) dice.push_back( six( engine ) );\n", + "\n", + " std::cout << std::count_if( dice.begin(), dice.end(), inRange(6,6) )\n", + " << \" of \" << NUM << \" inRange(6,6)\" << std::endl;\n", + "\n", + " std::cout << std::count_if( dice.begin(), dice.end(), inRange(4,6) )\n", + " << \" of \" << NUM << \" inRange(4,6)\" << std::endl;\n", + "\n", + " // remove all elements for 1 to 4\n", + " dice.erase( std::remove_if( dice.begin(), dice.end(), inRange(1,4) ),\n", + " dice.end() );\n", + "\n", + " // remove_if returns a past-the-end iterator for the new end of the range.\n", + "\n", + " std::cout << \"All numbers 5 and 6: \";\n", + " for( auto v: dice ) std::cout << v << \" \";\n", + "\n", + " std::cout << \"\\n\\n\";\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b45b0e4f", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra filter.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f85b1bc8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "4 inRange(5,10): false\n", + "5 inRange(5,10): true\n", + "\n", + "13 of 60 inRange(6,6)\n", + "38 of 60 inRange(4,6)\n", + "All numbers 5 and 6: 5 5 6 6 5 6 6 5 5 6 6 5 6 6 5 5 5 6 5 6 6 6 6 5 5 \n", + "\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "0ac98da0", + "metadata": {}, + "source": [ + "***\n", + "We had seen that we can store a lambda expression as an object (of some implicit type). However, we can also convert it into a\n", + "- function pointer\n", + "- std::function" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cf33e115", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing convert.cpp\n" + ] + } + ], + "source": [ + "%%file convert.cpp\n", + "\n", + "#include <iostream>\n", + "#include <functional>\n", + "\n", + "int main() {\n", + "\n", + " using fType = int (*) (int);\n", + "\n", + " using sType = std::function< int(int) >;\n", + "\n", + " auto impl = [] (int a) { return a+1; };\n", + "\n", + " std::cout << \" 1 + 1 = \" << impl( 1 ) << std::endl;\n", + "\n", + " fType expl1 = impl;\n", + " std::cout << \" 2 + 1 = \" << expl1( 2 ) << std::endl;\n", + "\n", + " sType expl2 = impl;\n", + " std::cout << \" 3 + 1 = \" << expl2( 3 ) << std::endl;\n", + "\n", + " std::cout << \"Type of impl is function pointer? \" \n", + " << std::boolalpha\n", + " << std::is_same< fType, decltype( impl ) >::value\n", + " << std::endl;\n", + "\n", + " std::cout << \"Type of expl1 is function pointer? \" \n", + " << std::is_same< fType, decltype( expl1 ) >::value\n", + " << std::endl;\n", + "\n", + " std::cout << \"Type of expl2 is instance of std::function? \" \n", + " << std::is_same< sType, decltype( expl2 ) >::value\n", + " << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3fbad575", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ convert.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0584e221", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1 + 1 = 2\n", + " 2 + 1 = 3\n", + " 3 + 1 = 4\n", + "Type of impl is function pointer? false\n", + "Type of expl1 is function pointer? true\n", + "Type of expl2 is instance of std::function? true\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "b4fc6130", + "metadata": {}, + "source": [ + "A closer look at line 12 shows that in the lambda expression, we did not explicitely specify a **return type**. In this case it will automatically be determined from the `return` statement.\n", + "\n", + "When we want or need to explicitely specify the return type, then this must be done via the *trailing-return-type* syntax." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ae57e2ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.000000" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "auto lFunc = [] (double a) -> double { return 2.0*a; };\n", + "lFunc( 5.0 )" + ] + }, + { + "cell_type": "markdown", + "id": "e04b7032", + "metadata": {}, + "source": [ + "***\n", + "In HyTeG we often make use of lambdas. Especially for initialising functions and setting boundary conditions. Like in this snippet: \n", + "```c++\n", + "real_t k = 2.0;\n", + "real_t m = 5.0;\n", + "\n", + "std::function< real_t(const Point3D&) > solFunc =\n", + " [k, m](const Point3D& x) { sin( k * pi * x[0] ) * sin( m * pi * x[1]; };\n", + "\n", + "P1Function< real_t > uExact( \"u_analytic\", storage, maxLevel, maxLevel );\n", + "uExact.interpolate( solFunc, maxLevel );\n", + "```" ] }, { "cell_type": "code", "execution_count": null, - "id": "8729d298", + "id": "328f2a8e", "metadata": {}, "outputs": [], "source": [] -- GitLab