Effective Modern C++:lambda expression

lambda expression can be understood as an anonymous function, it doesn’t need a name, it is created in runtime:

auto lam1 = []()
{
  cout<<"Hello World!";
};
lam1();

The code above shows a simplest lambda expression, when lam1() is called, it will print “Hello World!”.

How is the lambda expression existed in C++?

It is existed a closure class, each lambda is a unique closer class generated by the compiler in runtime. So it is a object and can be copied:

auto lam1_1 = lam1;
lam1_1();

when lam1_1 is called, it will also print “Hello World!”. Lambda turns the functions to be objects.

How to form a lambda

int cap1 = 1;
int cap2 = 3;
auto lam = [&,cap2](int arg)
{
  return cap1*cap2*arg; 
};
int result = lam(4);//result will be 12

Take the above code block as an example:

Capture List

1, Inside the [ ] is the so called “capture list

​ capture list is used to “capture” the variables in the current local scope, the first one in the capture list can be used as “default captured variable”. But the capture variables from the 2nd must be explicitly defined.

​ So in the above example, the first captured variable will be cap1, because we defined cap2 as a captured variable explicitly and at the same time we used cap1 in lambda, then of cause cap1 will be considered as the first/default captured variable.

​ If we defined all captured variables explicitly, which is preferred, we can not use the variables which are not defined as captured variable any more:

int cap1 = 1;
int cap2 = 3;
int cap3 = 4;
auto lam = [cap1, cap2](int arg)
{
  return cap1*cap2*cap3*arg; 
};//Wrong!

​ The above code block is wrong, it can not even be compiled, because cap3 is not captured by lambda lam. So in order to capture cap3, you can either leave the 1st position to be default captured variable or define it explicitly:

auto lam = [&,cap1, cap2](int arg)
{
  return cap1*cap2*cap3*arg; 
};//Correct!

auto lam = [cap3, cap1, cap2](int arg)
{
  return cap1*cap2*cap3*arg; 
};//Correct!

​ What’s more, leaving the 1st postion to be default captured variable can capture more than one variables!:

int cap1 = 1;
int cap2 = 3;
int cap3 = 4;
int cap4 = 5;
auto lam = [&, cap1, cap2](int arg) 
{
	return cap1*cap2*cap3*cap4*arg; 
};//Correct!
int result = lam(6);

​ The above code block is completly correct! You can see that cap3 and cap4 are not defined explicitly, but they are all correctly captured. Which means we can do the following for conveniency:

int cap1 = 1;
int cap2 = 3;
int cap3 = 4;
int cap4 = 5;
auto lam = [&](int arg) 
{
	return cap1*cap2*cap3*cap4*arg; 
};//Correct!
int result = lam(6);

By doing this, the lambda can capture as many variables as you like~

​ Capturring variables is just like passing intput arguments, & means capture by reference and = means capture by value:

int cap1 = 1;
auto lam = [&](int arg) 
{
	cap1 = 5;
	return cap1*arg; 
};//Capture by reference
int result = lam(6);

If capturing by refrence ( use [&] ), the result will be 30 and cap1 will be 5 after the lambda is called.

int cap1 = 1;
auto lam = [=](int arg) 
{
	cap1 = 5;
	return cap1*arg; 
};//Wrong, cap1 can not be changed
int result = lam(6);

If capturing by value ( use [=] ), you can not change the captured variable inside the lambda, so the above code block is wrong and it can not be compiled.

Attention!Capturing variables by reference should be avoided, because inside the lambda you don’t know if the captured variable is changed outside, the lambda is acturally sharing the same memory block with the other parts of the program.

Arguments List

Lambda supports auto as input arguments! The type will be deducted, this is the so called generic Lambda:

auto lam6 = [](auto arg1, auto arg2) {return arg1 * arg2; };
double r6 = lam6(2U, 1.2f);

Why do we need capture list?

Now we know that one big difference between normal function and lambda is that lambda has “capture list”. But why do we need capture list when we already get argument list?

One of the main reasons(actually I only know this…) is when lambda will be called by some others, its interface(input aruguments) is fixed, then you can not add more arguments, which means if you want to pass additional variables, you can only use capture list:

int comp = 4;
vector<int> values{ 1, 2, 3, 4, 5 };
auto item = find_if(values.begin(), values.end(), 
	[&](int value)
	{//the input argument int const& value is occupied by the STL function
		return value == comp; 
	}
);

The above code block shows a example when capture list is a must. We want to find the vector iterator whose value is equal to variable comp. In order to do this, we use the find_if STL function in .

The find_if function asks for the 3rd input argument to be a predicate, and it already defined the form of this predicate(can be a function or a lambda):

1 It has only one input argument, its type must be the same as the vector’s element.

2 It must return a boolean.

The find_if function will then return the iterator whose value can make the returned boolean to be true.

Now we use lambda as the predicate, then its argument list is fixed:

it must be (int value), if you define it to be (int value, int comp), it’s wrong.

So what if we want to use the variable comp in this lambda? Only one solution:we capture comp through the capture list.

Explicitly defining returned type

Another difference between lambda and a normal function is the returned type, if you noticed, the lambda expression doesn’t need a returned type.

Yes, the lambda will return a deducted type, for example:

auto lam = [](){return 3;};
lam();//return an int

in the above code block, lam() will return an int. This int is deducted by lambda.

What if we want a exact type? It is still possible:

auto lam = []()->double{return 3;};
lam();//return an double

add ”->double” after the arguments, you can define the returned type to be double.

Wangxin -->

Wangxin

I am algorithm engineer focused in computer vision, I know it will be more elegant to shut up and show my code, but I simply can't stop myself learning and explaining new things ...