C++14中的lambda简介 (generalized lambda-capture和generic lambda)

C++14的设计目标是"more complete C++11",也即是说,C++11还有很多不完善的地方。因此C++14不仅像C++03对C++98做出修正一样,也引入了不少新的特性。最为引入注目的,应该就是lambda的改变。

作为C++11中语法最为"奇特"的一个特性,lambda在C++14中主要有了两方面的不同。
首先一个改变是捕捉列表(capture-list)。在C++14中,捕捉列表中的变量可以有初始化表达式。我们可以看看标准中的例子:

[cpp] view plaincopy
  1.     int x = 4;  
  2.     auto y = [&r = x, x = x+1]()->int {  
  3.                 r += 2;  
  4.                 return x+2;  
  5.              }();  // Updates ::x to 6, and initializes y to 7.  

可以看到,捕捉列表中有两个变量,r和x。变量r是以引用方式进行捕捉,被赋予了初值x。不过r但并没有出现在父作用域,(这在C++11中是不可能的),在C++14中,它的类型由其初始化表达式推导而来。因此,r将成为父作用域中变量x的一个左值引用。而另外一个捕捉的变量x虽然是其父作用域的变量,却被赋予了初值x+1。由于其是以值捕捉的,可以想象得到,此x非父作用域的x,起初值应为5。

这里我们不仅就地定义了lambda函数,并立即调用了它,因此,最终获得的结果是x为6,y为7。大家可以试着把上面的lambda函数转换为等价的仿函数来体会一下新的规则。而由于lambda和仿函数的等价性,如果不确定lambda函数有什么样的限制,将其转化为等价的仿函数也就能够了解了。

这个特性又被称为"generalized lambda-capture",该特性主要来自于两篇proposal(n3610及n3648),本来是为了capture-by-move而设计的。而C++14这样的设计,不仅使得caputre-by-move能够实现,也使得定义capture-list更加灵活。

C++14中关于lambda的第二个改变则是所谓的"generic lambda"。(n3559和n3649)这实际上包含了两方面的内容,一方面是使用auto作为lambda函数的参数类型修饰符,另一方面则是允许带参数的lambda函数能够转化为普通函数。

我们首先看看auto作为lambda函数的参数类型修饰符的例子:

[cpp] view plaincopy
  1.     auto Identity = [](auto a) { return a; };  
  2.     int three = Identity(3);  
  3.     char const* hello = Identity("hello");  

在上面的例子中,我们定义了一个返回参数的lambda函数并赋给变量Identity,由于参数类型是auto,所以在调用Identity(3)的时候,lambda函数将实例化出一个以int为参数类型,int为返回值类型(注意lambda函数的返回值在C++11中就可以通过编译器推导了)的lambda函数版本。而在调用Identity("hello")的时候,则会实例化出一个以const char*为参数类型及返回值类型的lambda函数版本。这样一来,lambda函数就会具备了"模板"的特性。
而事实上,从实现上讲,C++14中的generic lambda也是以模板为基础的。上面的lambda可以被编译器实现为:

[cpp] view plaincopy
  1.     template<typename T>  
  2.     class lambda {  
  3.          public:  
  4.             T operator(T t) { return t; }  
  5.     };

的仿函数。(因此我们也使用了"实例化"这样的术语)。
generic lambda另外一个特性就是,带auto参数的lambda函数可以转换为函数指针了。比如:
[cpp] view plaincopy
  1.     auto Identity = [](auto a) { return a; };  
  2.     int (*fpi)(int) = Identity;  
  3.  

我们就可以将Identity赋值给函数指针。从实现上讲,这是通过重载仿函数的一个类型转换函数来完成的,具体的实现可以参看文档n3559。不过无论是C++11还是C++14,如果有捕捉列表的lambda函数都无法赋值给普通函数指针。这也是lambda与普通函数一个最大的区别:前者可以拥有初始状态,而后者则没有。

你可能感兴趣的:(C++)