From 684e43d9923372ed54cef38eedb9962f75513f79 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Thu, 16 Dec 2021 12:03:40 +0100 Subject: [PATCH] Adds link to an article on lambda functions --- notebooks/08_Functors+Lambdas.ipynb | 1157 ++++++++++++++++++++++++++- 1 file changed, 1156 insertions(+), 1 deletion(-) diff --git a/notebooks/08_Functors+Lambdas.ipynb b/notebooks/08_Functors+Lambdas.ipynb index 5c165fc..9fdc6f4 100644 --- a/notebooks/08_Functors+Lambdas.ipynb +++ b/notebooks/08_Functors+Lambdas.ipynb @@ -1 +1,1156 @@ -{"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":"# Functors, Lambdas and std::function","metadata":{},"id":"789d7a72"},{"cell_type":"markdown","source":"#### Function Pointers\nIn a previous example we had implemented a function two compare to objects of type ```myPair```.","metadata":{},"id":"008aa425"},{"cell_type":"code","source":"class myPair;\nbool cmp( const myPair& left, const myPair& right );","metadata":{},"execution_count":1,"outputs":[],"id":"96f5ab84"},{"cell_type":"markdown","source":"We needed that function to be able to create a set of ```myPair```'s.\n\n std::set< myPair, decltype( cmp )* > pairs ( cmp );\nWhat we pass to ```std::set```'s constructor is a **function pointer** of type\n\n bool (*) ( const myPair&, const myPair& )","metadata":{},"id":"ea6fcef6"},{"cell_type":"markdown","source":"***\n- This way to work with functions is something that C++ inherited from C.\n- Not only in the STL, but also in scientific programming we often have the case that we implement something, but want to leave some details to the user: \n - Consider e.g. implementing an algorithm to approximate the integral\n$$I = \\int_a^b f(x)\\, dx$$\n - When we code that we would like to leave the details of the integrand to be specified flexibly by the user. \n - As with std::set we can let the user provide a function that we then call inside our code.\n - This is known as a **callback**.\n- The main downside to using function (pointers) in this approach is that a free function is **stateless**. In order to influence its behaviour we have to pass arguments through its interface. That does not work nicely with the callback idea.\n - Note: We can add *state* to a function by making some of its local variables ```static```. See the ```accumulate``` examples.\n - However, a function is a global entity. Thus, there can always only be one state for it.","metadata":{},"id":"dc79e822"},{"cell_type":"markdown","source":"#### Functors\nIn C++ we can as one alternative use a **function object**, a.k.a. **functor**. This is an object of a class that overloads the function call operator ```operator()```.\n\n##### **Example 1:**","metadata":{},"id":"9ce08962"},{"cell_type":"code","source":"#include <iostream>\n\nclass greeter{\npublic:\n void operator() ( const std::string& str ) {\n std::cout << \"Greetings \" << str << std::endl;\n }\n};","metadata":{},"execution_count":2,"outputs":[],"id":"9bf146ec"},{"cell_type":"code","source":"int main() {\n greeter hello;\n hello( \"Class\" );\n}","metadata":{},"execution_count":3,"outputs":[],"id":"b13aa200"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":4,"outputs":[{"name":"stdout","output_type":"stream","text":"Greetings Class\n"}],"id":"f7a3701a"},{"cell_type":"markdown","source":"***\nNow that was a simple functor and did only demonstrate **how** it works and not **why** that approach can be advantageous.\n\n##### **Example 2:** \nIn our second example we will use a stateful functor that allows to perform different binary operations. The type of operation will be passed via its constructor.","metadata":{},"id":"58a187c4"},{"cell_type":"code","source":"#include <iostream>\n\nclass BinaryOperator {\n\npublic:\n\n typedef enum { ADD, MULT } type;\n\n BinaryOperator() = delete;\n BinaryOperator( BinaryOperator::type op ) : whatToDo( op ) {};\n\n int operator() ( int a, int b ) {\n switch( whatToDo ) {\n case ADD:\n return a+b;\n case MULT:\n return a*b;\n }\n }\n\nprivate:\n type whatToDo;\n\n};","metadata":{},"execution_count":5,"outputs":[],"id":"8c0f30c5"},{"cell_type":"code","source":"int main() {\n\n int a = 2;\n int b = 3;\n\n BinaryOperator adder( BinaryOperator::ADD );\n BinaryOperator multiplier( BinaryOperator::MULT );\n\n std::cout << adder( a, b ) << std::endl;\n std::cout << multiplier( a, b ) << std::endl;\n}","metadata":{},"execution_count":6,"outputs":[],"id":"05517c3a-eb4b-4827-9f74-54abf0264cca"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":7,"outputs":[{"name":"stdout","output_type":"stream","text":"5\n6\n"}],"id":"2777a5a1-f8bc-4907-90e2-488967071529"},{"cell_type":"markdown","source":"##### **Example 3:** \nSo how would our set example for myPair look like with a functor?","metadata":{},"id":"6adaab27"},{"cell_type":"code","source":"%%file functor.cpp\n\n#include <iostream>\n#include <set>\n\nstruct myPair {\n myPair( int fir, int sec ) : fir_( fir ), sec_( sec ) {\n std::cout << \"(\" << fir_ << \",\" << sec_ << \") constructed\" << std::endl;\n };\n int fir_;\n int sec_;\n};\n\nstruct myCompare {\n bool operator() ( const myPair& left, const myPair& right ) const {\n int val1 = left.fir_ * left.fir_ + left.sec_ * left.sec_;\n int val2 = right.fir_ * right.fir_ + right.sec_ * right.sec_;\n return val1 < val2;\n }\n};\n\nint main() {\n\n myCompare cmp;\n std::set< myPair, myCompare > pairs( cmp );\n\n myPair p1( 1, 2 );\n myPair p2( 3, 4 );\n\n std::cout << std::boolalpha;\n std::cout << \"p1 < p2 is \" << cmp( p1, p2 ) << std::endl;\n std::cout << \"p2 < p1 is \" << cmp( p2, p1 ) << std::endl;\n\n pairs.insert( p1 );\n pairs.insert( p2 );\n\n pairs.emplace( 3, 4 );\n pairs.emplace( 5, 6 );\n}","metadata":{},"execution_count":8,"outputs":[{"name":"stdout","output_type":"stream","text":"Overwriting functor.cpp\n"}],"id":"856ee747"},{"cell_type":"code","source":"!g++ -Wall -Wextra functor.cpp","metadata":{},"execution_count":9,"outputs":[],"id":"4adb7f0b"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":10,"outputs":[{"name":"stdout","output_type":"stream","text":"(1,2) constructed\n(3,4) constructed\np1 < p2 is true\np2 < p1 is false\n(3,4) constructed\n(5,6) constructed\n"}],"id":"f9bfde7f"},{"cell_type":"markdown","source":"#### std::function\n\nQuoting from cppreference:\n> Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.\n>\n> The stored callable object is called the target of std::function. If a std::function contains no target, it is called empty. Invoking the target of an empty std::function results in std::bad_function_call exception being thrown. \n\nThe functionality of ```std::function``` is quite useful especially in combination with lambda functions (see below).","metadata":{},"id":"c51deb94-f9c2-4a88-914b-ddf70f29601e"},{"cell_type":"markdown","source":"##### **Example 4:**\nAssign a simple free-function","metadata":{},"id":"6c584ecb-10c3-4ff2-ab53-2a66c0963009"},{"cell_type":"code","source":"#include <iostream>\nvoid sayIt() { std::cout << \"He said captain, I said wot\" << std::endl; }","metadata":{},"execution_count":1,"outputs":[],"id":"21120bbb-03a7-4048-97f4-0426c40c5bac"},{"cell_type":"code","source":"// generate an object of type std::function;\n\n// need this header file\n#include <functional>\n\n// need to supply the prototype of the function/invocation that we want to target\nstd::function< void( ) > sensible;","metadata":{},"execution_count":2,"outputs":[],"id":"bab0ab95-e5fd-4368-b3c2-77bcb869cfe0"},{"cell_type":"code","source":"// std::function overloads 'operator bool' so we can check if anything is targetted, yet\nif( !sensible ) {\n std::cout << \"empty -> nothing to do!\" << std::endl;\n}","metadata":{},"execution_count":3,"outputs":[{"name":"stdout","output_type":"stream","text":"empty -> nothing to do!\n"}],"id":"b4e3f68a-6c70-49e7-a04c-924804c4cb35"},{"cell_type":"code","source":"// now assign a target to our std::object\nsensible = sayIt;\n\n// and perform the invocation\nsensible();","metadata":{},"execution_count":5,"outputs":[{"name":"stdout","output_type":"stream","text":"He said captain, I said wot\n"}],"id":"c5b3e4d0-64b4-47c6-848f-f787bf9c0b17"},{"cell_type":"markdown","source":"##### **Example 5:**\nNow let us use a functor with a slightly more complicated interface","metadata":{},"id":"1ad030ff-9527-4436-be80-72baf9ac7b29"},{"cell_type":"code","source":"// that's our functor\nstruct Mult {\n int operator() ( int a, int b ) { return a*b; } \n}","metadata":{},"execution_count":6,"outputs":[],"id":"ff8d558b-da83-46d9-9842-8e0d9d5ba56d"},{"cell_type":"code","source":"// if we want, we can alias our std::function type\nusing binaryOp = std::function< int(int,int) >;\n\n// now instantiate an object that targets the functor\nbinaryOp multiply = Mult();\n\n// note that we needed the () to differentiate between the invocation and the functor itself!\n\nmultiply(2,3)","metadata":{},"execution_count":7,"outputs":[{"execution_count":7,"output_type":"execute_result","data":{"text/plain":"6"},"metadata":{}}],"id":"b5790b96-bf62-4d6e-ad9c-e56839882728"},{"cell_type":"markdown","source":"##### **Example 6:**\nA member function of a class can also serve as target","metadata":{},"id":"589b7083-7a36-4ff8-bdd0-c46f9994228c"},{"cell_type":"code","source":"struct ItemsInStore{\n int num;\n ItemsInStore() = delete;\n ItemsInStore( int num ) : num(num) {};\n void decrementAndShowRest() {\n std::cout << \"Only \" << --num << \" items left!\" << std::endl;\n }\n};","metadata":{},"execution_count":9,"outputs":[],"id":"646b302f-cdf5-40ed-98f8-01d98a9b277a"},{"cell_type":"code","source":"std::function< void( ItemsInStore& ) > takeOne = &ItemsInStore::decrementAndShowRest;","metadata":{},"execution_count":10,"outputs":[],"id":"b56b370a-654f-4665-a86d-1b4a1497a813"},{"cell_type":"code","source":"ItemsInStore candyBox( 5 );\ntakeOne( candyBox );\ntakeOne( candyBox );","metadata":{},"execution_count":11,"outputs":[{"name":"stdout","output_type":"stream","text":"Only 4 items left!\nOnly 3 items left!\n"}],"id":"b267e62f"},{"cell_type":"markdown","source":"#### Lambdas\nUsing *function pointers* or *functors* implies some significant coding overhead.\n\nC++11 introduced **lambda (functions)**, also known as **closures**, as a leight-weight alternative, that can be written inline in the source code.","metadata":{},"id":"9d2c9715"},{"cell_type":"markdown","source":"Let's start with an example","metadata":{},"id":"41bbe5a3"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <algorithm>\n\nstd::vector< int > numbers{1,2,3,4,5};\n\nfor_each( numbers.begin(), numbers.end(), []( int& n ) { n *= n; } );\n\nfor( int v: numbers ) std::cout << \" \" << v;","metadata":{},"execution_count":13,"outputs":[{"name":"stdout","output_type":"stream","text":" 1 4 9 16 25"}],"id":"24ad0ef1"},{"cell_type":"markdown","source":"The expression **`[]( int& n ) { n *= n; }`** is an *anonymous function*, 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\nlambda 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).","metadata":{},"id":"ff3d112c"},{"cell_type":"markdown","source":"##### Captures:\n\nThe `[ ]` 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\nSee e.g. <a href=\"https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture\">cppreference.com</a> for a full list of possibilities.","metadata":{},"id":"5764a9c9"},{"cell_type":"markdown","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.","metadata":{},"id":"645373e5"},{"cell_type":"code","source":"#include <iostream>\n\nint 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}","metadata":{},"execution_count":14,"outputs":[],"id":"3514f84c"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":15,"outputs":[{"name":"stdout","output_type":"stream","text":"\noriginal original\noriginal changed\n"}],"id":"7bca56ac"},{"cell_type":"markdown","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`.\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.","metadata":{},"id":"b237f560"},{"cell_type":"markdown","source":"***\nSmall detour: Coming back to what the type is. Let's try to figure out using `typeid`:","metadata":{},"id":"ac5c13c5"},{"cell_type":"code","source":"std::cout << \"5.0 is of type \" << typeid(5.0).name() << std::endl;\nint a;\nstd::cout << \"type of a is \" << typeid(a).name() << std::endl;\n\n\nauto lambda = [] (int a) { std::cout << a << std::endl; };\nstd::cout << \"What is lambda? \" << typeid(lambda).name() << std::endl;","metadata":{},"execution_count":34,"outputs":[{"name":"stdout","output_type":"stream","text":"5.0 is of type d\ntype of a is i\nWhat is lambda? ZN12__cling_N52216__cling_Un1Qu318EPvE3$_6\n"}],"id":"f0fd0543"},{"cell_type":"markdown","source":"Not very helpful, hugh. So maybe let's try something else","metadata":{},"id":"3393bdde"},{"cell_type":"code","source":"%%file lambda.cpp\n\nvoid test() {\n\nauto lambda = [] (int &a) { a++; };\n\nint b = 1;\nlambda(b); // avoid optimizing out\n}","metadata":{},"execution_count":18,"outputs":[{"name":"stdout","output_type":"stream","text":"Writing lambda.cpp\n"}],"id":"a32b1635"},{"cell_type":"code","source":"!g++ -c lambda.cpp","metadata":{},"execution_count":19,"outputs":[],"id":"85373b9f"},{"cell_type":"code","source":"!nm -C lambda.o","metadata":{},"execution_count":20,"outputs":[{"name":"stdout","output_type":"stream","text":" U _GLOBAL_OFFSET_TABLE_\n U __stack_chk_fail\n000000000000001e T test()\n0000000000000000 t test()::{lambda(int&)#1}::operator()(int&) const\n"}],"id":"fa038d35"},{"cell_type":"markdown","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`**.","metadata":{},"id":"ab79093e"},{"cell_type":"markdown","source":"***\nAnother example from *Modernes C++ in der Praxis*. Here we implement a join method for strings.","metadata":{},"id":"d65d40b3"},{"cell_type":"code","source":"%%file join.cpp\n\n#include <iostream>\n#include <algorithm>\n#include <string>\n#include <vector>\n\nstd::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\nint 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}","metadata":{},"execution_count":21,"outputs":[{"name":"stdout","output_type":"stream","text":"Writing join.cpp\n"}],"id":"03a872a9"},{"cell_type":"code","source":"!g++ -Wall -Wextra join.cpp","metadata":{},"execution_count":22,"outputs":[],"id":"933de670"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":23,"outputs":[{"name":"stdout","output_type":"stream","text":"\nOne\nOne Two\nOne:Two:Three\nOne/Two/Three/Four\nOneXXXTwoXXXThreeXXXFourXXXFive\n\n"}],"id":"c485b43f"},{"cell_type":"markdown","source":"Final example from Modernes C++ in der Praxis. Here we implement a flexible filter to operate on a vector of random numbers.","metadata":{},"id":"f839546e"},{"cell_type":"code","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\nstd::function< bool(int) > inRange( int low, int up ) {\n return [low,up] ( int d ) { return d >= low && d <= up; };\n}\n\n\nint 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() ); // a mersenne twister\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}","metadata":{},"execution_count":35,"outputs":[{"name":"stdout","output_type":"stream","text":"Overwriting filter.cpp\n"}],"id":"dcaec377"},{"cell_type":"code","source":"!g++ -Wall -Wextra filter.cpp","metadata":{},"execution_count":25,"outputs":[],"id":"b45b0e4f"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":28,"outputs":[{"name":"stdout","output_type":"stream","text":"\n4 inRange(5,10): false\n5 inRange(5,10): true\n\n7 of 60 inRange(6,6)\n30 of 60 inRange(4,6)\nAll numbers 5 and 6: 5 5 6 5 6 5 6 5 6 5 5 6 6 6 5 5 \n\n"}],"id":"f85b1bc8"},{"cell_type":"markdown","source":"***\nWe 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\n\nThe last one is what we implicitely did in the filter (generator function) `inRange()` in the previous example.","metadata":{},"id":"0ac98da0"},{"cell_type":"code","source":"%%file convert.cpp\n\n#include <iostream>\n#include <functional>\n\nint 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}","metadata":{},"execution_count":30,"outputs":[{"name":"stdout","output_type":"stream","text":"Writing convert.cpp\n"}],"id":"cf33e115"},{"cell_type":"code","source":"!g++ convert.cpp","metadata":{},"execution_count":31,"outputs":[],"id":"3fbad575"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":32,"outputs":[{"name":"stdout","output_type":"stream","text":" 1 + 1 = 2\n 2 + 1 = 3\n 3 + 1 = 4\nType of impl is function pointer? false\nType of expl1 is function pointer? true\nType of expl2 is instance of std::function? true\n"}],"id":"0584e221"},{"cell_type":"markdown","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\nWhen we want or need to explicitely specify the return type, then this must be done via the *trailing-return-type* syntax.","metadata":{},"id":"b4fc6130"},{"cell_type":"code","source":"auto lFunc = [] (double a) -> double { return 2.0*a; };\nlFunc( 5.0 )","metadata":{},"execution_count":33,"outputs":[{"execution_count":33,"output_type":"execute_result","data":{"text/plain":"10.000000"},"metadata":{}}],"id":"ae57e2ba"},{"cell_type":"markdown","source":"***\nIn <a href=\"https://i10git.cs.fau.de/hyteg/hyteg\">HyTeG</a> we often make use of lambdas. Especially for initialising functions and setting boundary conditions. Like in this snippet: \n```c++\nreal_t k = 2.0;\nreal_t m = 5.0;\n\nstd::function< real_t(const Point3D&) > solFunc =\n [k, m](const Point3D& x) { sin( k * pi * x[0] ) * sin( m * pi * x[1]; };\n\nP1Function< real_t > uExact( \"u_analytic\", storage, maxLevel, maxLevel );\nuExact.interpolate( solFunc, maxLevel );\n```","metadata":{},"id":"e04b7032"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"f5d8241e"}]} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "id": "789d7a72", + "metadata": {}, + "source": [ + "# Functors, Lambdas and std::function" + ] + }, + { + "cell_type": "markdown", + "id": "008aa425", + "metadata": {}, + "source": [ + "#### Function Pointers\n", + "In a previous example we had implemented a function two compare to objects of type ```myPair```." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "96f5ab84", + "metadata": {}, + "outputs": [], + "source": [ + "class myPair;\n", + "bool cmp( const myPair& left, const myPair& right );" + ] + }, + { + "cell_type": "markdown", + "id": "ea6fcef6", + "metadata": {}, + "source": [ + "We needed that function to be able to create a set of ```myPair```'s.\n", + "\n", + " std::set< myPair, decltype( cmp )* > pairs ( cmp );\n", + "What we pass to ```std::set```'s constructor is a **function pointer** of type\n", + "\n", + " bool (*) ( const myPair&, const myPair& )" + ] + }, + { + "cell_type": "markdown", + "id": "dc79e822", + "metadata": {}, + "source": [ + "***\n", + "- This way to work with functions is something that C++ inherited from C.\n", + "- Not only in the STL, but also in scientific programming we often have the case that we implement something, but want to leave some details to the user: \n", + " - Consider e.g. implementing an algorithm to approximate the integral\n", + "$$I = \\int_a^b f(x)\\, dx$$\n", + " - When we code that we would like to leave the details of the integrand to be specified flexibly by the user. \n", + " - As with std::set we can let the user provide a function that we then call inside our code.\n", + " - This is known as a **callback**.\n", + "- The main downside to using function (pointers) in this approach is that a free function is **stateless**. In order to influence its behaviour we have to pass arguments through its interface. That does not work nicely with the callback idea.\n", + " - Note: We can add *state* to a function by making some of its local variables ```static```. See the ```accumulate``` examples.\n", + " - However, a function is a global entity. Thus, there can always only be one state for it." + ] + }, + { + "cell_type": "markdown", + "id": "9ce08962", + "metadata": {}, + "source": [ + "#### Functors\n", + "In C++ we can as one alternative use a **function object**, a.k.a. **functor**. This is an object of a class that overloads the function call operator ```operator()```.\n", + "\n", + "##### **Example 1:**" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9bf146ec", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "class greeter{\n", + "public:\n", + " void operator() ( const std::string& str ) {\n", + " std::cout << \"Greetings \" << str << std::endl;\n", + " }\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b13aa200", + "metadata": {}, + "outputs": [], + "source": [ + "int main() {\n", + " greeter hello;\n", + " hello( \"Class\" );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f7a3701a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Greetings Class\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "58a187c4", + "metadata": {}, + "source": [ + "***\n", + "Now that was a simple functor and did only demonstrate **how** it works and not **why** that approach can be advantageous.\n", + "\n", + "##### **Example 2:** \n", + "In our second example we will use a stateful functor that allows to perform different binary operations. The type of operation will be passed via its constructor." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8c0f30c5", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "class BinaryOperator {\n", + "\n", + "public:\n", + "\n", + " typedef enum { ADD, MULT } type;\n", + "\n", + " BinaryOperator() = delete;\n", + " BinaryOperator( BinaryOperator::type op ) : whatToDo( op ) {};\n", + "\n", + " int operator() ( int a, int b ) {\n", + " switch( whatToDo ) {\n", + " case ADD:\n", + " return a+b;\n", + " case MULT:\n", + " return a*b;\n", + " }\n", + " }\n", + "\n", + "private:\n", + " type whatToDo;\n", + "\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "05517c3a-eb4b-4827-9f74-54abf0264cca", + "metadata": {}, + "outputs": [], + "source": [ + "int main() {\n", + "\n", + " int a = 2;\n", + " int b = 3;\n", + "\n", + " BinaryOperator adder( BinaryOperator::ADD );\n", + " BinaryOperator multiplier( BinaryOperator::MULT );\n", + "\n", + " std::cout << adder( a, b ) << std::endl;\n", + " std::cout << multiplier( a, b ) << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2777a5a1-f8bc-4907-90e2-488967071529", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "6\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "6adaab27", + "metadata": {}, + "source": [ + "##### **Example 3:** \n", + "So how would our set example for myPair look like with a functor?" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "856ee747", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting functor.cpp\n" + ] + } + ], + "source": [ + "%%file functor.cpp\n", + "\n", + "#include <iostream>\n", + "#include <set>\n", + "\n", + "struct myPair {\n", + " myPair( int fir, int sec ) : fir_( fir ), sec_( sec ) {\n", + " std::cout << \"(\" << fir_ << \",\" << sec_ << \") constructed\" << std::endl;\n", + " };\n", + " int fir_;\n", + " int sec_;\n", + "};\n", + "\n", + "struct myCompare {\n", + " bool operator() ( const myPair& left, const myPair& right ) const {\n", + " int val1 = left.fir_ * left.fir_ + left.sec_ * left.sec_;\n", + " int val2 = right.fir_ * right.fir_ + right.sec_ * right.sec_;\n", + " return val1 < val2;\n", + " }\n", + "};\n", + "\n", + "int main() {\n", + "\n", + " myCompare cmp;\n", + " std::set< myPair, myCompare > pairs( cmp );\n", + "\n", + " myPair p1( 1, 2 );\n", + " myPair p2( 3, 4 );\n", + "\n", + " std::cout << std::boolalpha;\n", + " std::cout << \"p1 < p2 is \" << cmp( p1, p2 ) << std::endl;\n", + " std::cout << \"p2 < p1 is \" << cmp( p2, p1 ) << std::endl;\n", + "\n", + " pairs.insert( p1 );\n", + " pairs.insert( p2 );\n", + "\n", + " pairs.emplace( 3, 4 );\n", + " pairs.emplace( 5, 6 );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4adb7f0b", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra functor.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f9bfde7f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1,2) constructed\n", + "(3,4) constructed\n", + "p1 < p2 is true\n", + "p2 < p1 is false\n", + "(3,4) constructed\n", + "(5,6) constructed\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "c51deb94-f9c2-4a88-914b-ddf70f29601e", + "metadata": {}, + "source": [ + "#### std::function\n", + "\n", + "Quoting from cppreference:\n", + "> Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.\n", + ">\n", + "> The stored callable object is called the target of std::function. If a std::function contains no target, it is called empty. Invoking the target of an empty std::function results in std::bad_function_call exception being thrown. \n", + "\n", + "The functionality of ```std::function``` is quite useful especially in combination with lambda functions (see below)." + ] + }, + { + "cell_type": "markdown", + "id": "6c584ecb-10c3-4ff2-ab53-2a66c0963009", + "metadata": {}, + "source": [ + "##### **Example 4:**\n", + "Assign a simple free-function" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "21120bbb-03a7-4048-97f4-0426c40c5bac", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "void sayIt() { std::cout << \"He said captain, I said wot\" << std::endl; }" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bab0ab95-e5fd-4368-b3c2-77bcb869cfe0", + "metadata": {}, + "outputs": [], + "source": [ + "// generate an object of type std::function;\n", + "\n", + "// need this header file\n", + "#include <functional>\n", + "\n", + "// need to supply the prototype of the function/invocation that we want to target\n", + "std::function< void( ) > sensible;" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b4e3f68a-6c70-49e7-a04c-924804c4cb35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "empty -> nothing to do!\n" + ] + } + ], + "source": [ + "// std::function overloads 'operator bool' so we can check if anything is targetted, yet\n", + "if( !sensible ) {\n", + " std::cout << \"empty -> nothing to do!\" << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c5b3e4d0-64b4-47c6-848f-f787bf9c0b17", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "He said captain, I said wot\n" + ] + } + ], + "source": [ + "// now assign a target to our std::object\n", + "sensible = sayIt;\n", + "\n", + "// and perform the invocation\n", + "sensible();" + ] + }, + { + "cell_type": "markdown", + "id": "1ad030ff-9527-4436-be80-72baf9ac7b29", + "metadata": {}, + "source": [ + "##### **Example 5:**\n", + "Now let us use a functor with a slightly more complicated interface" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ff8d558b-da83-46d9-9842-8e0d9d5ba56d", + "metadata": {}, + "outputs": [], + "source": [ + "// that's our functor\n", + "struct Mult {\n", + " int operator() ( int a, int b ) { return a*b; } \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b5790b96-bf62-4d6e-ad9c-e56839882728", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "// if we want, we can alias our std::function type\n", + "using binaryOp = std::function< int(int,int) >;\n", + "\n", + "// now instantiate an object that targets the functor\n", + "binaryOp multiply = Mult();\n", + "\n", + "// note that we needed the () to differentiate between the invocation and the functor itself!\n", + "\n", + "multiply(2,3)" + ] + }, + { + "cell_type": "markdown", + "id": "589b7083-7a36-4ff8-bdd0-c46f9994228c", + "metadata": {}, + "source": [ + "##### **Example 6:**\n", + "A member function of a class can also serve as target" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "646b302f-cdf5-40ed-98f8-01d98a9b277a", + "metadata": {}, + "outputs": [], + "source": [ + "struct ItemsInStore{\n", + " int num;\n", + " ItemsInStore() = delete;\n", + " ItemsInStore( int num ) : num(num) {};\n", + " void decrementAndShowRest() {\n", + " std::cout << \"Only \" << --num << \" items left!\" << std::endl;\n", + " }\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b56b370a-654f-4665-a86d-1b4a1497a813", + "metadata": {}, + "outputs": [], + "source": [ + "std::function< void( ItemsInStore& ) > takeOne = &ItemsInStore::decrementAndShowRest;" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b267e62f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Only 4 items left!\n", + "Only 3 items left!\n" + ] + } + ], + "source": [ + "ItemsInStore candyBox( 5 );\n", + "takeOne( candyBox );\n", + "takeOne( candyBox );" + ] + }, + { + "cell_type": "markdown", + "id": "9d2c9715", + "metadata": {}, + "source": [ + "#### 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." + ] + }, + { + "cell_type": "markdown", + "id": "41bbe5a3", + "metadata": {}, + "source": [ + "Let's start with an example" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "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 function*, 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": 14, + "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": 15, + "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`.\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": 34, + "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? ZN12__cling_N52216__cling_Un1Qu318EPvE3$_6\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": 18, + "id": "a32b1635", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing 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": 19, + "id": "85373b9f", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -c lambda.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "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": 21, + "id": "03a872a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing 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": 22, + "id": "933de670", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra join.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "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": 35, + "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() ); // a mersenne twister\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": 25, + "id": "b45b0e4f", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra filter.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "f85b1bc8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "4 inRange(5,10): false\n", + "5 inRange(5,10): true\n", + "\n", + "7 of 60 inRange(6,6)\n", + "30 of 60 inRange(4,6)\n", + "All numbers 5 and 6: 5 5 6 5 6 5 6 5 6 5 5 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\n", + "\n", + "The last one is what we implicitely did in the filter (generator function) `inRange()` in the previous example." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "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": 31, + "id": "3fbad575", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ convert.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "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": 33, + "id": "ae57e2ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10.000000" + ] + }, + "execution_count": 33, + "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 <a href=\"https://i10git.cs.fau.de/hyteg/hyteg\">HyTeG</a> 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": "markdown", + "id": "e69e15aa", + "metadata": {}, + "source": [ + "If you want to read more on lambda functions take a look at <a href=\"https://www.cprogramming.com/c++11/c++11-lambda-closures.html\">Lambda Functions in C++11 - the Definitive Guide</a> by Alex Allain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03edb82f", + "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 +} -- GitLab