VS2010 lambda
时间:2010-10-17 来源:BinChengfei
与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;
}