=== Function Pointers === * Below we have an example that uses function pointers (taken from quantstart). How does it work? #include double add(double left, double right) { return left + right; } double multiply(double left, double right) { return left * right; } double binary_op(double left, double right, double (*f)(double, double)) { return (*f)(left, right); } int main( ) { double a = 5.0; double b = 10.0; std::cout << "Add: " << binary_op(a, b, add) << std::endl; std::cout << "Multiply: " << binary_op(a, b, multiply) << std::endl; return 0; } * What is the meaning of the syntax "double (*f)(double, double)"? * Can you assign functions to variables? #include double add(double left, double right) { return left + right; } double multiply(double left, double right) { return left * right; } int main( ) { double a = 5.0; double b = 10.0; auto f = add; std::cout << "Add: " << f(a, b) << std::endl; f = multiply; std::cout << "Multiply: " << f(a, b) << std::endl; return 0; } * What if we did not want to use 'auto'. What's the type of the functions? int main( ) { double a = 5.0; double b = 10.0; // // This is how we declare a function pointer: double (*f)(double, double) = add; std::cout << "Add: " << f(a, b) << std::endl; f = multiply; std::cout << "Multiply: " << f(a, b) << std::endl; return 0; } * How are function pointers useful? - They let us parameterize operations! * Can you give an example? #include #include void foreach(std::vector vec, void (*f)(int)) { for (int &elem: vec) { f(elem); } } void print(int elem) { std::cout << elem << std::endl; } int main( ) { std::vector vec = {1, 2, 3}; foreach(vec, print); } * Can you make the foreach function generic? #include #include template void foreach(std::vector vec, void (*f)(T)) { for (T &elem: vec) { f(elem); } } void print(double elem) { std::cout << elem << std::endl; } int main( ) { std::vector vec = {1, 2, 3}; foreach(vec, print); } * And print, can you make it generic? #include #include template void foreach(std::vector vec, void (*f)(T)) { for (T &elem: vec) { f(elem); } } template void print(T elem) { std::cout << elem << std::endl; } int main( ) { std::vector vec_int = {1, 2, 3}; std::vector vec_dbl = {1.1, 2.2, 3.3}; std::vector vec_str = {"1_1", "2_2", "3_3"}; foreach(vec_int, print); foreach(vec_dbl, print); foreach(vec_str, print); } * But, can you pass a different function to foreach? * Can you think on a different function that you would like to use? #include #include template void foreach(std::vector vec, void (*f)(T)) { for (T &elem: vec) { f(elem); } } template void print(T elem) { std::cout << elem << std::endl; } int counter = 0; template void greater_than_0(T elem) { if (elem > 0) { counter++; } } int main( ) { std::vector vec_int = {1, -2, 3, -4}; foreach(vec_int, print); foreach(vec_int, greater_than_0); std::cout << ">0: " << counter << std::endl; } * Would it be possible to write a function to increment the elements of the vector? - Not really: the pointer "void (*f)(T)" receives its arguments by value. * Can you modify the example to make incrementing the elements possible? #include #include template void foreach(std::vector& vec, void (*f)(T&)) { for (T &elem: vec) { f(elem); } } template void print(T& elem) { std::cout << elem << std::endl; } template void inc(T& elem) { elem++; } int main( ) { std::vector vec_int = {1, -2, 3, -4}; foreach(vec_int, print); foreach(vec_int, inc); foreach(vec_int, print); } === Functional Objects === * Let's analyze again this function that counts occurrences of certain conditions in the elements stored in the vector. This function has a state. What's its state? int counter = 0; template void greater_than_0(T elem) { if (elem > 0) { counter++; } } * Would it be possible to move this state inside the function? template void greater_than_0(T elem) { static counter; counter = 0; if (elem > 0) { counter++; } } * Would the above piece of code work? * How would you get the value of 'counter' outside the function? * What if two threads invoked greater_than_0? * It is possible to use functional objects to simulate functions with state. Do you know what is a functional object? - It's an object from a class that defined the operator "()" * Can you redefine the program that uses greater_than_0 to use functional objects instead? #include #include template class Function { public: virtual void operator() (T elem) = 0; }; template class PrintFunction: public Function { public: virtual void operator() (T elem) { std::cout << elem << std::endl; } }; template class GT0Function: public Function { public: GT0Function(): _counter(0) { } virtual void operator() (T elem) { if (elem > 0) { _counter++; } } unsigned getCounter() const { return _counter; } private: unsigned _counter; }; template void foreach(std::vector vec, Function& f) { for (T &elem: vec) { f(elem); } } * And how do you use these objects? Can you implement the 'main' function? int main( ) { std::vector vec_int = {1, -2, 3, -4}; PrintFunction printer; GT0Function gt0er; foreach(vec_int, printer); foreach(vec_int, gt0er); std::cout << ">0: " << gt0er.getCounter() << std::endl; } * Why did you have to write an abstract class? - To pass the different functional objects to the foreach function. * Can you implement that program that uses binary operations using functional objects? #include class BinaryFunction { public: BinaryFunction() {}; virtual ~BinaryFunction() {}; virtual double operator() (double left, double right) = 0; }; class Add : public BinaryFunction { public: Add() {}; virtual double operator() (double left, double right) { return left+right; } }; class Multiply : public BinaryFunction { public: Multiply() {}; virtual double operator() (double left, double right) { return left*right; } }; double binary_op(double left, double right, BinaryFunction* bin_func) { return (*bin_func)(left, right); } int main( ) { double a = 5.0; double b = 10.0; BinaryFunction* pAdd = new Add(); BinaryFunction* pMultiply = new Multiply(); std::cout << "Add: " << binary_op(a, b, pAdd) << std::endl; std::cout << "Multiply: " << binary_op(a, b, pMultiply) << std::endl; delete pAdd; delete pMultiply; return 0; } * So, which syntax is better: function pointers or functional objects? * What's the problem with these two approaches? - We need to predefine the functions. They need to have a name, for instance. === Lambda Expressions === * What is the problem with the object approach? - We need to write a lot of code. What if this code is only used once? * Have you heard of lambda expressions? - That is a function without a name (ok, that's not the whole story... but it will have to do for now). * Can you re-write the above code to use lambda expressions? #include int main() { double a = 5.0; double b = 10.0; // // Declaring two lambda expressions: auto add = [](double x, double y)->double {return x + y;}; auto mul = [](double x, double y)->double {return x * y;}; // // Using the lambda expressions: std::cout << "Add: " << add(a, b) << std::endl; std::cout << "Mul: " << mul(a, b) << std::endl; return 0; } * Can you explain the syntax of lambda expressions? [ capture clause ] (parameters) -> return-type { body } - where: [capture clause]: This part we will see later. Let's assume always [] for now. (parameters): the arguments of the anonymous function return-type: the type of the value returned by the function (can be void) { body }: the body of the function * Can you replace auto with the actual types of the variables in the first example using lambdas? #include int main() { double a = 5.0; double b = 10.0; // // Declaring two lambda expressions: double (*add)(double, double) = [](double x, double y)->double {return x+y;}; double (*mul)(double, double) = [](double x, double y)->double {return x*y;}; // // Using the lambda expressions: std::cout << "Add: " << add(a, b) << std::endl; std::cout << "Mul: " << mul(a, b) << std::endl; return 0; } * Is there any difference to this type and the type of a function, as we had seen before? * Can you write the functions directly at the places where they are applied? #include // // Let's use std directly, to fit the code within an 80-column file. using namespace std; int main() { double a = 5.0; double b = 10.0; cout << "Add: " << [](double x, double y)->double {return x+y;}(a, b) << endl; cout << "Mul: " << [](double x, double y)->double {return x*y;}(a, b) << endl; return 0; } * Ok, can you try to sue the [ capture clause ]? #include int main(int argc, char** argv) { auto mul2Argc = [argc](int x){return x * argc;}; int x[] = {2, 3, 5, 7, 11}; for (int e: x) { std::cout << "$> " << mul2Argc(e) << std::endl; } } * What is [argc] doing in this example? * What happens if we remove argc from the capture clause? * Consider the program below, that uses function pointers. What would be the advantage of writing it using lambdas? #include #include template void foreach(std::vector vec, void (*f)(T)) { for (T &elem: vec) { f(elem); } } template void print(T elem) { std::cout << elem << std::endl; } int main( ) { std::vector vec_int = {1, 2, 3}; std::vector vec_dbl = {1.1, 2.2, 3.3}; std::vector vec_str = {"1_1", "2_2", "3_3"}; foreach(vec_int, print); foreach(vec_dbl, print); foreach(vec_str, print); } * And how can you do it? #include #include template void foreach(std::vector vec, std::function f) { for (T &elem: vec) { f(elem); } } int main() { auto print = [](auto x){ std::cout << x << std::endl;}; std::vector vec_int = {1, 2, 3}; std::vector vec_dbl = {1.1, 2.2, 3.3}; std::vector vec_str = {"1_1", "2_2", "3_3"}; foreach(vec_int, print); foreach(vec_dbl, print); foreach(vec_str, print); return 0; } * That's a really mysterious syntax: "std::function f". What is it? - that's how we declare a lambda expression: std::function * Can you compile this program with C++11? - No. Only C++14 and up! * Can you add in the function that counts up positive values? #include #include template void foreach(std::vector vec, std::function f) { for (T &elem: vec) { f(elem); } } int main() { std::vector vec_int = {1, -2, 3, -4}; auto print = [](auto x){ std::cout << x << std::endl;}; // // The lambda expression that will count positive values: unsigned count; auto counter = [&count](auto x){ if (x > 0) { count++; } }; // // Testing the program: foreach(vec_int, print); count = 0; foreach(vec_int, counter); std::cout << "$> " << count << std::endl; return 0; } * What is the meaning of [&count] in the declaration of the function? * Would this program work if we remove it?