2018年10月20日 星期六

C++ 學習筆記:Lambda

"Hello World" Lambda Example:



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>

class LambdaMutable { // this is a function object
private:
    int id; // copy from outside
public:
    LambdaMutable(int _id) : id(_id) {}
    void operator() () {
        std::cout << "id: " << id << std::endl;
        id++;
    }
};

void BinderExample()
{
    auto plus10times = std::bind(std::multiplies<int>(),
        std::bind(std::plus<int>(),
        std::placeholders::_1,
        10),
        2);
    auto plus10time2ByLambda = [](int i) {
        return (i + 10) * 2;
    };

    std::cout << "BinderExample: "
        << "binder=" << plus10times(1)
        << " lambda=" << plus10time2ByLambda(1)
        << std::endl;
}

/*** Type of Lambdas ***/
std::function<int(int, int)> returnLambda() {
    return[](int x, int y) {
        return x * y;
    };
}

int main()
{
    /*** Hello world ***/
    [] {
        std::cout << "Hello, world! (Directly call)" << std::endl;
    }();

    auto lambdaPrintHelloWorld = [] {
        std::cout << "Hello, world! (Call by auto)" << std::endl;
    };
    lambdaPrintHelloWorld();

    /*** Parameter ***/
    auto lambdaPrintString = [](const std::string& str) {
        std::cout << str << std::endl;
    };
    lambdaPrintString("Hello lambda!");

    /*** Return type ***/
    [] { // no return type, it is deduce by return value, this case is int
        return 42;
    };

    []() -> double {
        return 42;
    };

    std::vector<int> vec = { 1, -2, 3, -4, 5 };
    auto easyPrint = [](int i) {
        std::cout << "vec contains " << i << std::endl;
    };
    std::transform(vec.begin(), vec.end(), vec.begin(),
        [](int i) { return i < 0 ? -i : i; }); // return type deduced.
    std::for_each(vec.begin(), vec.end(), easyPrint);

    std::transform(vec.begin(), vec.end(), vec.begin(),
        [](int i) { if (i < 0) return i; else return -i; }); // return type deduced.
    std::for_each(vec.begin(), vec.end(), easyPrint);

    /*** Capture ***/
    int x = 0;
    int y = 123;
    auto lambdaCap = [x, &y] {
        //x = 10; // compiler error
        y++; // ok!
        std::cout << "x: " << x << std::endl;
        std::cout << "y: " << y << std::endl;
    };

    x = y = 456;
    lambdaCap();
    lambdaCap();

    auto lambdaCap2 = [=, &y] {
        //x = 10; // compiler error
        y++; // ok!
        std::cout << "x: " << x << std::endl;
        std::cout << "y: " << y << std::endl;
    };

    /*** Mutable ***/
    int id = 0;

    auto lambdaMutable = [id]() mutable {
        std::cout << "id: " << id << std::endl;
        id++;// ok!
    };
    id = 123;
    lambdaMutable(); // print "id: 0"

    LambdaMutable obj(id);
    id = 456;
    obj();   // print "id: 123"

    /*** Type of Lambdas ***/
    auto returnLambdaFuc = returnLambda();
    std::cout << returnLambdaFuc(5, 6) << std::endl;

    /*** Binder v.s. Lambda ***/
    BinderExample();
}

Lambda introducer

Lambda introducer: 左右方框 [] ,稱作 capture
  • 用來在 lambda 內部存取外部 non-static 物件
  • static 物件,例如 std::cout,可直接使用


Lambda Optional

Lambda introducer [] 與函式主體 { ... } 中間可以放:
  • parameter
  • mutable
  • exception specification
  • attribute specifiers
  • return type (trailing return type)
若其中一項出現,則需強制加上 parameter 的左右括號 ( )
[...] (...) mutable throwSpec -> retType {...}
若沒指定 return type,lambda 會自動從回傳值 deduce


Capture(access to outer scope)

Implicitly Capture
  • [=] 外部 scope 透過 pass-by-value 傳入 lambda,外部物件在 lambda 內為 read-only
    • 採用 capture by value 的前提是外部物件有支援拷貝,例如 ostream 不支援拷貝,僅能使用 capture by reference
    • capture 的外部物件會在 lambda 創建時拷貝,並非呼叫時拷貝
  • [&] 外部 scope 透過 pass-by-reference 傳入 lambda,外部物件在 lambda 內為 read-write
    • 採用 capture by reference,必須確保 lambda 執行時外部物件仍是存在的
Explicitly Capture
  • 指定傳入的物件與方式,例如,[x, &y]
Both
  • 搭配混用,例如 [=, &y, &z]
    • [&, identifier_list],identifier_list 內只可用 =
    • [=, identifier_list],identifier_list 內只可用 &
  • 必須 = 或 & 開頭,作為 default capture 行為
  • 必須交叉使用,例如 [&, =x] 或 [=, &os]


Mutable

  • [=] or [x] 變成 read-write


Lambda 行為

可比擬為一個具有以下項目的 class:
  • private data member: 與 capture 傳入的 Object 相同
  • overload operator ()
  • 若有宣告 mutable,則 operator() 會是 non-const member function,否則是 const member function


Lambda 型別

  • 一個 anonymous function object or functor,且每個 lambda 的型別是唯一的
  • 因為唯一性,宣告 type 必須用 std::function template 或 auto
  • 若需要此 type,可以使用 decltype() 取得
auto cmp = [](const Person& p1, const Person &p2) {
    return (p1.lastname() < p2.lastname()) ||
           (p1.lastname() == p2.lastname() && p1.firstname() < p2.firstname());
};

std::set<Person, decltype(cmp)> coll(cmp);
// decltype(cmp) is type of lambda
// cmp is sorting criteria
  • Functional programming 情況下,可使用 std::function<> 定義一個 general type


其他

  • 作為 algorithm 的 predicate 或 sorting criterion
auto l = [=](int i) {
    return i > x && i < y;
};
auto pos = std::find_if(coll.cbegin(), coll.cend(), l);


  • Lambda 不擁有 internal state,需透過 capture 與 pass-by-reference 搭配外部 scope 物件做到
  • 與 binder 比較:
auto plus10times = std::bind(std::multiplies<int>(),
        std::bind(std::plus<int>(),
           std::placeholders::_1,
           10),
        2);
auto plus10time2ByLambda = [](int i) {
    return (i + 10) * 2;
};
  • Lambda 不可以有 default argument
  • Lambda 不可以為 template


Reference

  1. The C++ Standard Library: A Tutorial and Reference
  2. C++ Primer

沒有留言:

張貼留言