180 likes | 416 Views
Lambda Expressions. Conceptually, unnamed function objects created “on the flyâ€. vector<int> v; auto it = find_if(v.cbegin(), v.cend(), [](int i){return i>0 && i<10;} ); deque<float> d; sort(d.begin(), d.end(), [](const float& f1, const float& f2) { return f1 < f2;} );.
E N D
Lambda Expressions • Conceptually, unnamed function objects created “on the fly”. vector<int> v; auto it = find_if(v.cbegin(), v.cend(), [](int i){return i>0 && i<10;}); deque<float> d; sort(d.begin(), d.end(), [](const float& f1, const float& f2) { return f1 < f2;});
Lambda Expressions • Lambda expressions – just a shorthand for creating and using function objects. • Such function objects are called closures.
Capturing Local Variables • Local variables from the calling context can be “captured” int minVal, maxVal; .. auto it = find_if(v.begin(), v.end(), [minVal, maxVal](int i) {return i>minVal && i<maxVal;}); Without lambdas, you would need to create a function object and pass minVal and maxVal through its constructor.
Capturing local variables • Captures can also be references. Different local variables can be captured in different ways: int minVal, maxVal; .. auto it = find_if(v.begin(), v.end(), [&minVal, &maxVal](int i) {return i>minVal && i<maxVal;}); auto it = find_if(v.begin(), v.end(), [minVal, &maxVal](int i) {return i>minVal && i<maxVal;});
Capturing Local Variables • Capture mode defaults can be specified: auto it = find_if(v.begin(), v.end(), [=](int i) //defaults to by value {return i>minVal && i<maxVal;}); auto it = find_if(v.begin(), v.end(), [&](int i) //defaults to by ref {return i>minVal && i<maxVal;}); • With a default capture mode, captured variables need not be listed.
Capturing Local Variables • Default overridable on per-variable basis: auto it = find_if(v.begin(), v.end(), [=, &maxVal](int i) {return i>minVal && i<maxVal;});
Capturing Class Members • To access class members within a member function, capture this: class Widget { public: void doSomething(); private: list<int> il; int minVal;}; Void Widget::doSomething() { auto it = find_if(il.begin(), il.end(), [minVal](int i){return i >minVal;})} //error Void Widget::doSomething() { auto it = find_if(il.begin(), il.end(), [this](int i){return i >minVal;})}
Capturing Class Members • A default capture mode also makes this available: class Widget { public: void doSomething(); private: list<int> il; int minVal;}; Void Widget::doSomething() { auto it = find_if(il.begin(), il.end(), [=](int i){return i >minVal;})} Void Widget::doSomething() { auto it = find_if(il.begin(), il.end(), [&](int i){return i >minVal;})}
Lambda Return Types • Optional when: • Return type is void. • Lambda body is “return expr;” • Return type is that of “expr” • Otherwise must be specified via trailing return type syntax: vector<double> v; transform(v.begin(), v.end(), [](double d)->double { doSomethingElseHere(); return sqrt(abs(d));});
Trailing Return Types • Must be used with lambdas (when a return type is given). • Often useful with decltype • Permitted for any function (with a leading auto):\ void f(int x); auto f(int x)->void; class Widget { public: void mf1(int x); auto mf2()const -> bool; }; • Non-lambda, non-decltype uses not expected to be common
Lambdas without Parameter Lists • Lambdas with no parameters may omit the parameter list. • Such functions especially useful with threads. Void doSomeWork(); Void doMoreWork(); thread t1([](){doSomeWork(); doMoreWork();}); thread t2([] {doSomeWork(); doMoreWork();}); • Omitting the optional parentheses seems to be fairly common.
Lambda Expression Complexity • Lambdas maybe arbitrarily complex: • Multiple statements, multiple returns. • Throw/catch exceptions. • Essentially anything allowed in a “normal” function. • Maintainability considerations suggest: • Short, clear, context-derived lambdas are best.
Storing Closures • Closure types not specified, but two easy ways to store closures: 1. auto auto multipleOf5 =[](long x) {return x %5==0;}; vector<long> vl; vl.erase(remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());
Storing Closures • Closure types not specified, but two easy ways to store closures: 2. std::function std::function<bool(long)> multipleOf5 =[](long x){return x %5==0;}; vl.erase(remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());
Specifying Function Types • A function type is its declaration w/o any names: void f1(int x); //type is void(int) double f2(int x, string s); //type is double(int, string) • C++ uses this function type for std::function std::function<bool(long)> multipleOf5 = [](long x) {return x % 5 == 0;}; • Function types never use the trailing return type format.
Lambda/Closure Summary • Lambda Expressions generate closures. • Calling state can be captured by value or by reference. • Return types, when specified, use trailing return type syntax. • Closure can be stored using auto or std::function. • Short, clear, context-derived lambdas are best.