Tuesday, October 21, 2014

Grammar candy: Lambda expression (closures) in C++11

Useful links: Alex Allain's blog. Cheatsheet (Chinese).

Basic structure of lambda expression:
[capture specification](argument list)->return type {function body}

Example:
int main() {
    auto f = [](){cout<<"hello world";};
    f();
    return 0;
}
Argument list is what we needed in the lambda function. Just like:
int main() {
    auto f = [](int v){cout<<v<<endl;};
    f(1024);
    return 0;
}
Capture specification is fun, it means we can use external data/variable (with respect to lambda function itself) in the lambda function. Based on the way we pass the external data/variable, there are several cases:
  1. [], Empty: No external data/variable is needed in the current lambda function. Just like all the cases above;
  2. [=]: Pass all visible data/variable with respect to the lambda function into lambda function, by value.
    int main() {
        int a = 1;
        int b = 2;
        auto f = [=](int c){cout<<(c+a)<<' '<<(c+b);};
        f(10);
        return 0;
    }
    
    Output 11 and 12.
  3. [&]: Same as above, only difference is pass by reference.
    int main() {
        int a = 1;
        auto f = [&]{++a;};
        f();
        cout<<a;
        return 0;
    }
    Output 2.
  4. [this]: pass lambda function class to lambda function.
    struct s{
        int v;
        s(int val):v(val){};
        void f(){ ( [this]{cout<<++v;} )(); }
    };
    
    int main() {
        (new s(3))->f();
        return 0;
    }
    Output 4.
  5. [var_name]: Only pass variable var_name to lambda, by value.
  6. [&var_name]: Same as above, only difference is pass by reference.
  7. [a, &b]: Pass variable a by value, b by reference.
  8. [=, &a, &b]: Pass variable a and b by reference, all other visible variables pass by value.
  9. [&, a, b]: Inverse of case 8.
    int main() {
        int a = 1;
        int b = 2;
        int c = 3;
        ([=, a, &b]{cout<<a<<b++<<c<<endl;}) ();
        cout<<a<<b<<c;
        return 0;
    }
    Output 123,133. Don't try to to ++a inside lambda function, this will throw an error stating a is read only.
Return type:
When lambda contains more than one return value, C++ standards say you better give it a return type, just like:
int main() {
    auto abs = [](int a)->int{if(a>=0)return a;
                           else return -a;};
    cout<<abs(-3)<<abs(-2)<<abs(-1)<<abs(0);
    return 0;
}
If you try this in ideone, it will not complain if you missed the ->int part, but that is because of GCC. C++ standard say you should explicitly denote the return type.

Alex also presents a very fun concept, Delegate.I am still a newbie, thus I'll use Alex's case and build a similar test case:

#include <iostream>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;

struct c1{
    vector<int> v;
    c1(vector<int> val):v(val){}
    
    void modifyV(function<string (int)> functionHandle) {
        if(functionHandle) {
            for_each(v.begin(), v.end(), \
            [&](int e){cout<<functionHandle(e);});
        }
    }
};

struct c2{
    int record;
    c2():record(0){};
    string c2func(int val) {
        record = max(record, val);
        return val%2?"odd ":"even ";
    }
    void disp() {
        cout<<"\nmax number is "<<record;
    }
};

int main() {
    vector<int> v{1,2,3,4,5};
    c1 t(v);
    c2 t2;
    t.modifyV([&](int val){return t2.c2func(val);});
    t2.disp();
    return 0;
}
Output is:
odd even odd even odd 
max number is 5
So what I did was I use class c2 to process c1's data (determine whether it is odd number or even number) and record c1's data's maximum number. The connection is built by code on line 34. This lambda function use & to take in t2 and passed it to t.

This is quite fun, a more interesting code can be found in Alex's post. He also talks about how to receive a lambda function as input by using std::functional<type>, function handle as parameter.

No comments:

Post a Comment