天天看點

VS2010的Lambda表達式

前言:

與boost支援lambda不同的是,VS2010提供對lambda的内建支援。

 Lambdas

在 C++ 0x 中,“lambda 表達式”隐式定義并建構不具名函數對象,這些對象就像手寫函數對象一樣。下面是 lambda “Hello,World”入門級的示例:

C:/Temp>type meow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

OutPut:

0 1 2 3 4 5 6 7 8 9

[] 操作符是 lambda 導引符, 它告訴編譯器一個 lambda 表達式開始了。 (int n) 是 lambda 參數聲明,它告訴編譯器不具名函數對象類的函數調用操作符帶有哪些參數, { cout << n << " "; }  是複合聲明,它是不具名函數對象類的函數調用操作符的函數體。不具名函數對象類的函數調用操作符預設傳回 void。

這樣,C++0x 在内部将它轉換成如你在C++ 98 下編寫的一樣代碼:

C:/Temp>type meow98.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

struct LambdaFunctor {

    void operator()(int n) const {

        cout << n << " ";

    }

};

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    for_each(v.begin(), v.end(), LambdaFunctor());

    cout << endl;

}

OutPut:

0 1 2 3 4 5 6 7 8 9

現在我将不再累述類似“不具名函數對象類的函數調用操作符預設傳回 void”這樣的話,開始換用“lambda 函數傳回 void”的說法,但是記住 lambda 表達式做了些什麼是很重要的,那就是:定義類并建構對象。

當然,lambda 的複合聲明部分(函數體部分)可以包含多個聲明語句,譬如:

C:/Temp>type multimeow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    for_each(v.begin(), v.end(), [](int n) {

        cout << n;

        if (n % 2 == 0) {

            cout << " even ";

        } else {

            cout << " odd ";

        }

    });

    cout << endl;

}

OutPut:

0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

lambda 函數也并不總是必須傳回 void。如果 lambda 的複合聲明語句像是這樣的 { return expression; } ,那麼 lambda 的傳回類型就會自動地被推斷成 expression 的類型。

C:/Temp>type cubicmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    deque<int> d;

    transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });

    for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

OutPut:

729 512 343 216 125 64 27 8 1 0

在這裡,  n * n * n 的類型是 int,是以 lambda 函數傳回 int。

有着複雜複合聲明語句的 lambda 函數不會自動推斷傳回類型,你必須顯式指定傳回類型。

C:/Temp>type returnmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    deque<double> d;

    transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {

        if (n % 2 == 0) {

            return n * n * n;

        } else {

            return n / 2.0;

        }

    });

    for_each(d.begin(), d.end(), [](double x) { cout << x << " "; });

    cout << endl;

}

OutPut:

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

 -> double 是可選的 lambda 傳回類型從句。為什麼它不放在左邊(譯注:傳回類型一般在函數左邊聲明),就像程式員一直以來在C函數中做的那樣?因為那樣的話 lambda 導引符 [] 就不會第一個出現了,而正是它告訴編譯器一個 lambda 函數開始了。(核心工作組最擅長解決這樣的問題;嘗試猜測C++ 中一個給定的概念是否是可被解析的會讓我頭疼。)

如果忘記了指定 lambda傳回類型從句,編譯器就會抱怨每一個傳回語句:

C:/Temp>cl /EHsc /nologo /W4 borkedreturnmeow.cpp

borkedreturnmeow.cpp

borkedreturnmeow.cpp(20) : error C3499: a lambda that has been specified to have a void return type cannot return a value

borkedreturnmeow.cpp(22) : error C3499: a lambda that has been specified to have a void return type cannot return a value

到目前為止我所介紹的 lambda 都是無狀态的:它們不包含資料成員。你也可以有有狀态的 lambda,這是通過“傳遞”(原文用加引号的 capturing 這個詞,在這裡我翻譯成傳遞似乎不太妥,故我都加括号引用原文,下同)局部變量來實作的。空的 lambda 導引符 [] 意味着“一個無狀态的 lambda”,但在 lambda 導引符 [] 中你可以指定 capture-list :

C:/Temp>type capturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 0;

    int y = 0;

    // op>>() leaves newlines on the input stream,

    // which can be extremely confusing. I recommend

    // avoiding it, and instead using non-member

    // getline(cin, str) to read whole lines and

    // then parse them. But in the interests of

    // brevity, I'll use evil op>>():

    cout << "Input: ";

    cin >> x >> y;

    v.erase(remove_if(v.begin(), v.end(), [x, y](int n) { return x < n && n < y; }), v.end());

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

Input: 4 7

OutPut:

0 1 2 3 4 7 8 9

如果你忘記了capture-list,編譯器就會抱怨:

C:/Temp>cl /EHsc /nologo /W4 borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp(27) : error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

borkedcapturekittybyvalue.cpp(27) : error C3493: 'y' cannot be implicitly captured as no default capture mode has been specified

(我很快就會解釋預設的傳遞(capture))

記着,lambda 表達式隐式地定義了一個不具名函數對象類。複合聲明語句 { return x < n && n < y; } 在這個類中被當作函數調用操作符的函數體。雖然從詞法結構上看複合聲明語句是在 main() 塊之内,但在概念上它是在 main() 塊之外的,是以如果不傳遞(capture)到 lambda 中去,就不能在其中使用來自main() 中的局部變量。

上面的代碼在内部被翻譯成:

C:/Temp>type capturekittybyvalue98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

class LambdaFunctor {

public:

    LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }

    bool operator()(int n) const { return m_a < n && n < m_b; }

private:

    int m_a;

    int m_b;

};

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 0;

    int y = 0;

    cout << "Input: ";

    cin >> x >> y; // EVIL!

    v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

}

Input: 4 7

OutPut:

0 1 2 3 4 7 8 9

在這裡你可以清楚 地看到是“按值”傳遞(captures)的。函數對象存儲了局部變量的拷貝。這就使得函數對象可以比通過傳遞(capture)來建立它們的局部變量有 更長的生命期。但是,要注意:(a)在 lambda 中不能修改通過傳遞(capture)獲得的拷貝,因為預設情況下函數調用操作符是 const 屬性的,(b)一些對象的拷貝開銷是昂貴的,(c)局部變量的更新不會反應到通過傳遞(capture)獲得的拷貝(在語義上它們是原始值)。很快我就會 解釋如有需要應該如何來處理以上情況。

但是首先,你可以 “按值傳遞(capture)任何東西”,而不用特别指明每一個你想要傳遞(capture)的局部變量。其文法是使用這種形式的 lambda 導引符 [=] (預設傳遞(capture-default) = 應該可以讓你想起指派或者拷貝初始化 Foo foo = bar; 雖然這裡的拷貝實際上是直接初始化(通過初始化清單進行指派),就像上面的 m_a(a))。

C:/Temp>type defaultcapturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 0;

    int y = 0;

    cout << "Input: ";

    cin >> x >> y; // EVIL!

    v.erase(remove_if(v.begin(), v.end(), [=](int n) { return x < n && n < y; }), v.end());

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

Input: 4 7

OutPut:

0 1 2 3 4 7 8 9

當編譯器看到 lambda 中的 x 和 y, 就會從 main() 中按值傳遞(capture)。

情形(a)要修改通過傳遞(capture)獲得拷貝該怎樣呢?預設情況下,一個 lambda 函數調用操作符是 const 屬性的,但是可以通過使用 mutable 把它變成 non-const。

C:/Temp>type capturekittybymutablevalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 1;

    int y = 1;

    for_each(v.begin(), v.end(), [=](int& r) mutable {

        const int old = r;

        r *= x * y;

        x = y;

        y = old;

    });

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

    cout << x << ", " << y << endl;

}

OutPut:

0 0 0 6 24 60 120 210 336 504

1, 1

這裡是依次将 v 中前兩個元素相乘。(我得承認真的很難想出一個不用 partial_sum() 或 adjacent_difference() 表示的例子,partial_sum() 是與前面所有的元素相乘,adjacent_difference()是與前一個元素相乘)。注意到情形(d),對通過傳遞獲得的拷貝的更新操作并沒有影 響局部變量(再一次,原始值語義)。

如果你想處理情形(b),(c)和(d):避免拷貝,在 lambda 中觀察局部變量的更新,以及在 lambda 中修改局部變量又該怎麼做呢?在這種情況下,你會想通過引用傳遞(capture by reference)。其文法是這種形式的 lambda 導引符 [&x, &y] (你可以把它想象成  X& x, Y& y ; 那是“引用”而不是“取址”)。

C:/Temp>type capturekittybyreference.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 1;

    int y = 1;

    for_each(v.begin(), v.end(), [&x, &y](int& r) {

        const int old = r;

        r *= x * y;

        x = y;

        y = old;

    });

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

    cout << x << ", " << y << endl;

}

OutPut:

0 0 0 6 24 60 120 210 336 504

8, 9

注意與 capturekittybymutablevalue.cpp 的差別:(1),lambda 導引符是 [&x, &y] ,(2)沒有 mutable,(3),局部變量 x 和 y 最後的值是 8 和 9,反應了在 lambda 中對他們的修改。

上面的代碼在内部被翻譯成:

C:/Temp>type capturekittybyreference98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

#pragma warning(push)

#pragma warning(disable: 4512) // assignment operator could not be generated

class LambdaFunctor {

public:

    LambdaFunctor(int& a, int& b) : m_a(a), m_b(b) { }

    void operator()(int& r) const {

        const int old = r;

        r *= m_a * m_b;

        m_a = m_b;

        m_b = old;

    }

private:

    int& m_a;

    int& m_b;

};

#pragma warning(pop)

int main() {

    vector<int> v;

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

    int x = 1;

    int y = 1;

    for_each(v.begin(), v.end(), LambdaFunctor(x, y));

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

    cout << x << ", " << y << endl;

}

原文位址:http://www.cppblog.com/kesalin