Lambda expressions
Lambda expressions are one of the most important features in modern C++, and Lambda expressions actually provide a feature similar to anonymous functions. Anonymous functions are used when they need a function but do not want to trouble naming a function. There are actually many such scenarios, so anonymous functions are almost standard in modern programming languages.
Base
The basic syntax of Lambda expressions is as follows:
[Capture List] (Parameter List) mutable (optional) Exception Properties -> Return Type {
// Function body
}
Except for the [Capture List], the other parts of the above syntax rules are easy to understand. However, the function name of the general function is omitted, and the return value is performed in the form of -> (we have mentioned this writing method in the end return type before the previous section).
The so-called capture list can actually be understood as a type of parameter. The function body inside the Lambda expression cannot use variables outside the function body by default. At this time, the capture list can play the role of passing external data. According to the behavior of the transmission, the capture list is also divided into the following types:
1. Value capture
Similar to parameter passing values, the prerequisite for value capture is that variables can be copied, the difference is that the captured variable is copied when the Lambda expression is created, rather than when it is called:
void lambda_value_capture() { int value = 1; auto copy_value = [value] { return value; }; value = 100; auto stored_value = copy_value(); std::cout << "stored_value = " << stored_value << std::endl; // At this time, stored_value == 1, and value == 100. // Because copy_value saved a copy of value when it was created}
2. Quote Capture
Similar to reference passing parameters, reference capture saves references and the value changes.
void lambda_reference_capture() { int value = 1; auto copy_value = [&value] { return value; }; value = 100; auto stored_value = copy_value(); std::cout << "stored_value = " << stored_value << std::endl; // At this time, stored_value == 100, value == 100. // Because copy_value saves references}
3. Implicit Capture
Manually writing a capture list is sometimes very complicated. This mechanical work can be left to the compiler to handle. At this time, you can write a & or = in the capture list to declare to the compiler to use reference capture or value capture.
To sum up, capturing provides the function of using Lambda expressions to use external values. The four most commonly used forms of capturing lists can be:
a. [] Empty capture list
b. [name1, name2, ...] Capture a series of variables
c. [&] Reference capture, let the compiler deduce the reference list itself
d. [=] Value capture, let the compiler deduce the value capture list by itself
4. Expression capture
This part of the content requires understanding the rvalue references and smart pointers that will be mentioned soon.
The above-mentioned value capture and reference capture are variables that have been declared in the outer scope, so these capture methods all capture lvalues, and rvalues cannot be captured.
C++14 gives us convenience, allowing the captured members to be initialized with arbitrary expressions, which allows the capture of rvalues. The declared capture variable type will be judged based on the expression, and the judgment method is essentially the same as using auto:
#include <iostream> #include <memory> // std::make_unique #include <utility> // std::move void lambda_expression_capture() { auto important = std::make_unique<int>(1); auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int { return x+y+v1+(*v2); }; std::cout << add(3,4) << std::endl; }
In the above code, important is an exclusive pointer that cannot be captured by the "=" value. At this time, we can transfer it to an rvalue and initialize it in the expression.
Generic Lambda
In the previous section, we mentioned that the auto keyword cannot be used in the parameter table, because such writing will conflict with the function of the template. However, Lambda expressions are not ordinary functions, so Lambda expressions cannot be templated without explicitly specifying the parameter table type. Fortunately, this trouble only exists in C++11. Starting with C++14, the formal parameters of the Lambda function can use the auto keyword to generate generics in the sense:
auto add = [](auto x, auto y) { return x+y; }; add(1, 2); add(1.1, 2.2);
Function object wrapper
Although this part of the content is part of the standard library, it essentially enhances the ability of the C++ language runtime, and this part of the content is also very important, so I will introduce it here.
std::function
The essence of a Lambda expression is an object (called a closure object) of a class type similar to the function object type (called a closure object). When the capture list of the Lambda expression is empty, the closure object can also be converted into a function pointer value for passing, for example:
#include <iostream> using foo = void(int); // Define function type, see the alias syntax in the previous section for the usevoid functional(foo f) { // The function type defined in the parameter list is considered to be the degenerated function pointer type foo* f(1); //Calling the function through the function pointer} int main() { auto f = [](int value) { std::cout << value << std::endl; }; functional(f); // Pass closure object, implicitly convert to function pointer value of type foo* f(1); // lambda expression call return 0; }
The above code gives two different forms of calling. One is to pass Lambda as a function type for calling, while the other is to directly call Lambda expressions. In C++11, these concepts are unified and the types of objects that can be called are uniformly called callable types. And this type is introduced through std::function.
C++11 std::function is a general, polymorphic function encapsulation. Its instances can store, copy and call any target entity that can be called. It is also a type-safe package of existing callable entities in C++ (relatively speaking, the call of function pointers is not type-safe), in other words, a function container. When we have a function container, we can more conveniently process functions and function pointers as objects. For example:
#include <functional> #include <iostream> int foo(int para) { return para; } int main() { // std::function wraps a function with a return value of int and a parameter of int std::function<int(int)> func = foo; int important = 10; std::function<int(int)> func2 = [&](int value) -> int { return 1+value+important; }; std::cout << func(10) << std::endl; std::cout << func2(10) << std::endl; } std::bind and std::placeholder
std::bind is used to bind the parameters of function calls. The requirement it solves is that sometimes we may not be able to obtain all the parameters of calling a function at one time. Through this function, we can bind some of the calling parameters to the function in advance and become a new object, and then complete the call after the parameters are complete. For example:
int foo(int a, int b, int c) { ; } int main() { // Bind parameters 1 and 2 to function foo, // But use std::placeholders::_1 to placeholders auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2); // When calling bindFoo at this time, you only need to provide the first parameter bindFoo(1); }
Tip: Pay attention to the wonderful use of the auto keyword. Sometimes we may not be familiar with the return value type of a function, but we can avoid this problem by using auto.
This is the end of this article about the detailed explanation of C++ lambda functions. For more related C++ lambda content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!