My favorite tool in C ++ is lambda functions, although somehow they told me that they seem scary. In fact they are lovely. They greatly simplify the writing of programs and allow you to make quite interesting decisions.
But before considering various ways of using lambda functions, I suggest to familiarize yourself with the basic syntax of lambda functions.
Possible options for the syntax of lambda functions
[ capture ] ( params ) mutable exception attribute -> ret { body } [ capture ] ( params ) -> ret { body } [ capture ] ( params ) { body } [ capture ] { body }
The first option is complete, but it is not forbidden to use abbreviated variations in the recording of functions.
- capture - list of external capture objects, they can be captured both by reference and by copying.
- params - list of parameters passed to the lambda function, this part will be similar to writing arguments for normal functions.
- mutable - Using mutable allows you to modify copies of objects that have been captured by copying. In the normal version, they will not be modified.
- exception - provides an exception specification, that is, lambda functions as well as normal functions can throw exceptions.
- attribute - provides an attribute specification, there are only two such attributes defined in the C ++ specification ([[noreturn]], [[carries_dependency]])
- params - list of parameters passed to the lambda function
- ret - the return value of the lambda function
As for the return value, it can be automatically output from the object type, which is returned by the return statement. If the lambda function does not have a return statement, then the return value will be void.
The lambda function creates an unnamed temporary object of a unique unnamed non-union, non-aggregate type, known as a closure type. Thanks to the introduction of the auto operator in the modern C ++ standard, it is possible to declare the lambda object object fairly easily, without writing a std :: function declaration with all aprams and return values, which makes the code more simple and readable (for an experienced programmer, of course. that the newcomer will quickly suspect that something is wrong if the std :: function appears in the lambda declaration, but this is already a matter of practice).
Here is an example of declaring a simple lambda function that will return a void type, since at least one return statement is missing.
#include <iostream> using namespace std; int main() { auto myLambda = []() { cout << "Hello World!" << endl; }; myLambda(); return 0; }
Accordingly, the program code is not compiled if there are two or more return statements in the lambda function that will return objects of different types that are not connected by an inheritance hierarchy and which can not be reduced to a base class type. And even if these objects have a base class, it will be necessary to register the type of the return value, it will just be a pointer to the object of the base class (in the general case).
Here is an example of code that will not compile.
#include <iostream> using namespace std; class A { public: A(){} }; class B : public A { public: B(){} }; class C : public A { public: C(){} }; int main() { auto myLambda = [](int type) { if (type == 0) { return new B(); } else { return new C(); } }; myLambda(0); return 0; }
You must specify the return type
auto myLambda = [](int type) -> A* // Let's specify the type of the return value { if (type == 0) { return new B(); } else { return new C(); } };
Also, the compilation error will occur if you do not specify the return type and you create an object inside the lambda function in the heap, but in some cases you can return the pointer to nullptr . That is, the following code is not compiled.
#include <iostream> using namespace std; class A { public: A(){} }; int main() { auto myLambda = [](int type) { if (type == 0) { return new A(); } else { return nullptr; } }; myLambda(0); return 0; }
Again you need to specify the type of the return value
auto myLambda = [](int type) -> A* // Let's specify the type of the return value { if (type == 0) { return new A(); } else { return nullptr; } };
The fact is that nullptr is a universal data type, which in some sense is not a data type, because it can not be set as a variable type. But it can be assigned as a value to a pointer to an object. In order for the implicit conversion to occur correctly in this case, you must also specify the type of the return value.
Also in the above example, we show how to call a lambda function and pass parameters to it. Noticed? In this example, the int type parameter is used, depending on which we return a pointer to the created object or nullptr .
Also in lambda functions there is the concept of capturing variables. This means that the lambda function can use not only variables that are passed to it as parameters, but also any objects that have been declared outside the lambda function.
The symbol list can be transferred as follows:
**[a,&b]**
where a is captured by value, and b is captured by reference.**[this]**
grabs the this pointer by value.**[&]**
capture all characters by reference**[=]**
capture all characters by value**[]**
nothing captures
About the capture of variables, let's talk in the next articles.
But I note one interesting point, the lamda function can be called immediately where you declared it, if you add parentheses after the body of the lame function and pass all the necessary parameters, if any.
For example, this code is also compiled
#include <iostream> using namespace std; class A { public: A(){} }; int main() { A* myA = [](int type) -> A* { if (type == 0) { return new A(); } else { return nullptr; } }(0); return 0; }
So think about whether the following code is compiled?
int main(){[](){}();}