C++ 函数篇 lambda表达式

以下为从wiki、侯捷老师的深入STL、stackoverflow中research的资料。

wiki

c++11提供了匿名函数的支持,叫做lambda表达式,形式如下:

1
[capture](parameters) mutable exception attribute -> return_type {body}

必须用[]括起来的capture列表来开始一个lambda表达式的定义。

  • lambda函数的形参表比普通函数的形参表多了三条限制:

    • 参数不能有缺省值
    • 不能有可变长参数列表
    • 不能有无名参数
  • 如果没有形参,且没有mutable、exception、attribute声明,参数的圆括号可以省略。如果有声明,即使参数为空,也不能省略。

  • 如果函数体就只有一个return语句,或者返回值为void,那么返回值类型声明可以省略:

1
[capture](para){body}

Example

1
2
3
4
[](int x,int y){return x+y;}//return中隐式获得返回值类型,返回值类型是decltype(x+y)。
[](int& x){++x;}//没有return语句,返回值为void
[](){++global_x;}
[]{++global_x;}//没有参数,可以省略()

闭包

  • lambda函数可以捕获lambda函数外的具有automatic storage duration的变量,即函数的局部变量与函数形参变量。函数体与这些变量的集合合起来称做闭包)。
  • 这些外部变量在声明lambda表达式时列在在方括号[]中。空的方括号表示没有外界变量被capture或者按照默认方式捕获外界变量。这些变量被传值捕获或者引用捕获。
  • 对于传值捕获的变量,默认为只读(这是由于lambda表达式生成的为一个函数对象,它的operator()成员缺省有const属性)。修改这些传值捕获变量将导致编译报错。
  • 但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值捕获变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。
  • lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。
  • 编译器生成lambda函数对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。
1
2
3
4
5
6
[]//没有定义任何变量,但是必须列出空的方括号。lambda表达式中尝试使用任何外部变量都会导致编译错误。
[x,&y] //x按值传递,y按引用传递
[&]//任何被使用的外部变量都按引用传递
[=]//任何被使用的外部变量都按值传递
[&,x]//x按值传入,其它变量按引用传入
[=,&z]//z按引用传入,其它变量按值传入

Example

1
2
3
4
vector<int> some_list{1,2,3,4,5};
int total=0;
for_each(begin(some_list),end(some_list),
[&total](int x){total+=x;});

this指针

在类的非静态成员函数中定义的lambda表达式可以显式或隐式捕捉this指针,从而可以引用所在类对象的数据成员与函数成员。

lambda函数的函数体中,可以访问下述变量:

  • 函数参数
  • 局部声明的变量
  • 类数据成员:要求lambda表达式声明在类成员函数中,对象的this指针必需显式捕获声明。
  • 具有静态存储期的变量(如全局变量)
  • 被捕获的外部变量
  • 显式捕获的变量
  • 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。

模板类型

lambda函数的数据类型是函数对象,保存时必须用std::function模板类型或auto关键字。

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
#include <vector>

#include <functional>
#include <iostream>
using namespace std;
double eval(std::function <double(double)> f, double x = 2.0)
{
return f(x);
}

int main()
{
std::function<double(double)> f0 = [](double x) {return 1; };
auto f1 = [](double x) {return x; };
decltype(f0) fa[3] = { f0,f1,[](double x) {return x*x; } };
std::vector<decltype(f0)> fv = { f0,f1 };
fv.push_back([](double x) {return x*x; });//fv.size()=3
for (int i = 0; i<fv.size(); i++)
std::cout << fv[i](2.0) << std::endl;//1,2,4
for (int i = 0; i<3; i++)
std::cout << fa[i](2.0) << std::endl;//1,2,4
for (auto &f : fv)
std::cout << f(2.0) << std::endl;//1,2,4
for (auto &f : fa)
std::cout << f(2.0) << std::endl;//1,2,4
std::cout << eval(f0) << std::endl;//1
std::cout << eval(f1) << std::endl;//2
std::cout << eval([](double x) {return x*x; }) << std::endl;//4
return 0;
}

C++11 侯捷

  • C++ 11 introdued lamndas,allowing the definition of inline functionality,which can be used as a parameter or a local object.

  • Lambdas change the way the C++ standard library is used.

    仿函数。其实是一个对象类型。用()可以直接调用,一个仿函数对象。

  • A lambda is a definition of functionality that can be defined inside statements and expressions.

  • You can use a lambda as an inline function.

  • The minimal lambda funcion hsa no paramemter and simply does something:

Lambdas

  • The type of a lambda is an anonymous function object(or functor) that is unique for each lambda expression.

  • To declare objects of that type,you need templates or auto.

  • If you need type ,you can use decltype(),which is ,required to pass a lambda as hash function or ordering or sorting criterion to associative or unordered containers.

1
2
3
4
5
6
7
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);
  • You need the type of the lambda for the declaration of the set, decltype must be used,which yields the type of a lambda object,such as cmp.

  • Note that you also have to pass the lambda object to the constructor of coll;otherwise,coll would call the default constructor for the sorting criterion(准则) passed,and by rule lambdas have no default constructor and no assignment operator.(lambda没有默认构造函数,也没有赋值操作。)

  • So,for a sorting criterion,a class defining the function objects might still be more intuitive.(如果用lambda写排序准则,由于没有默认构造函数,所以使用的时候容易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class Key,
class Compare=less<Key>,
class Alloc=alloc>
class set{
public:
//typedefs:
...
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type,value_type,
identity<value_type>,
key_compare,Alloc>rep_type;
rep_type t;//red-black tree representing set
public:
...
set():t(Compare()){}//调用默认构造函数
explicit set(const Compare& comp):t(const){}
}
  • Function objects are a very powerful way to customize the behavior of STL algorithms,and can encapsulate both code and data (unlike plain functions).
  • But Function objects are inconvenient to define because of the need to entire classes.
  • Moreover,they are not defined in the place in yout source code where you are trying to use them, and the non-locality makes them more difficult to use.
  • Libraries have attempted to mitigate(减轻) some of the problems of verbosity(冗长) and non-locality ,but dont offer much help because the syntax becomes complicated and the compiler errors are not very friendly.
  • Using function objects from libraries is also less efficient since the function objects defined as data members are not in-lined.
  • Lambda expressions address these problems.

  • The following code snippet shows a lambda expression used in a program to integers between variables x and y from a vector of integers.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    vector<int> vi{5,28,50,83,70,590,245,59,24};
    int x = 30;
    int y = 100;
    vi.erase(remove_if(vi.begin(),vi.end(),
    [x,y](int n){return x<n&&n<y;}),
    vi.end());//把(30,100)内的数字拿掉
    for(auto i:vi)
    cout<<i<<'';//5 28 590 245 24
    cout<<endl;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;
    };
    v.erase(remove_if(v.begin(),v.end(),
    LambdaFunctor(x,y)),
    v.end());

stackoverflow

ref:stackoverflow

Q:

1
2
3
What is a lambda expression in C++11? When would I use one? What class of problem do they solve that wasn't possible prior to their introduction?

A few examples, and use cases would be useful.

A:

The problem

  • C++ includes useful generic functions like std::for_each and std::transform, which can be very handy.

  • Unfortunately they can also be quite cumbersome to use, particularly if the functor you would like to apply is unique to the particular function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <algorithm>
#include <vector>

namespace {
struct f {
void operator()(int) {
// do something
}
};
}

void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}
  • If you only use f once and in that specific place it seems overkill to be writing a whole class just to do something trivial and one off.

  • In C++03 you might be tempted to write something like the following, to keep the functor local:

1
2
3
4
5
6
7
8
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}

however this is not allowed, f cannot be passed to a template function in C++03.

The new solution

C++11 introduces lambdas allow you to write an inline, anonymous functor to replace the struct f. For small simple examples this can be cleaner to read (it keeps everything in one place) and potentially simpler to maintain, for example in the simplest form:

1
2
3
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda functions are just syntactic(句法) sugar for anonymous functors.

Return types

In simple cases the return type of the lambda is deduced(推导) for you, e.g.:

1
2
3
4
5
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}

however when you start to write more complex lambdas you will quickly encounter cases where the return type cannot be deduced by the compiler, e.g.:

1
2
3
4
5
6
7
8
9
10
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}

->:explicitly specify a return type

To resolve this you are allowed to explicitly specify a return type for a lambda function, using -> T:

1
2
3
4
5
6
7
8
9
10
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}

“Capturing” variables

So far we’ve not used anything other than what was passed to the lambda within it, but we can also use other variables, within the lambda. If you want to access other variables you can use the capture clause (the [] of the expression), which has so far been unused in these examples, e.g.:

1
2
3
4
5
6
7
8
9
10
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}

You can capture by both reference and value, which you can specify using & and =respectively。(见闭包)

The generated operator() is const by default, with the implication(意义) that captures will be const when you access them by default. This has the effect that each call with the same input would produce the same result, however you can mark the lambda as mutable to request that the operator() that is produced is not const.

Thanks for Support.