From 9e2c89ea1405906ccb679424a679bbd19dd92f90 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Tue, 30 Nov 2021 16:33:08 +0100 Subject: [PATCH] Starts new notebook on functors, lambda functions and std::function --- notebooks/08_Functors+Lambdas.ipynb | 338 ++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 notebooks/08_Functors+Lambdas.ipynb diff --git a/notebooks/08_Functors+Lambdas.ipynb b/notebooks/08_Functors+Lambdas.ipynb new file mode 100644 index 0000000..2f4d489 --- /dev/null +++ b/notebooks/08_Functors+Lambdas.ipynb @@ -0,0 +1,338 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "789d7a72", + "metadata": {}, + "source": [ + "# Functors, Lambdas and std::function" + ] + }, + { + "cell_type": "markdown", + "id": "43efc656", + "metadata": {}, + "source": [ + " \n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "008aa425", + "metadata": {}, + "source": [ + "#### Function Pointers\n", + "In a previous example we had implemented a function to compare to objects of type ```myPair```." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "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", + " \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()```." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "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": 21, + "id": "b13aa200", + "metadata": {}, + "outputs": [], + "source": [ + "int main() {\n", + " greeter hello;\n", + " hello( \"Class\" );\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "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": 23, + "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": "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": 25, + "id": "856ee747", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing 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 << \"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": 29, + "id": "4adb7f0b", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -Wextra functor.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "f9bfde7f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1,2) constructed\n", + "(3,4) constructed\n", + "p1 < p2 is 1\n", + "p2 < p1 is 0\n", + "(3,4) constructed\n", + "(5,6) constructed\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "fe907464", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "6\n" + ] + } + ], + "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", + "}\n", + "\n", + "main();" + ] + }, + { + "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": "code", + "execution_count": null, + "id": "8729d298", + "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