Lambda expressions are one of the most important features in modern C++, and Lambda expressions actually provide a feature like anonymous functions.
Anonymous functions are used when a function is needed, but you don't want to use a function to name a function. There are actually many, many scenes like this.
So anonymous functions are almost standard on modern programming languages.
### Basics
The basic syntax of a Lambda expression is as follows:
constint&b=std::move(1);// legal, const lvalue reference can
std::cout<<b<<std::endl;
}
```
The first question, why not allow non-linear references to bind to non-lvalues?
This is because there is a logic error in this approach:
```cpp
voidincrease(int&v){
v++;
}
voidfoo(){
doubles=1;
increase(s);
}
```
Since `int&` can't reference a parameter of type `double`,
you must generate a temporary value to hold the value of `s`.
Thus, when `increase()` modifies this temporary value,
`s` itself is not modified after the call is completed.
The second question, why do constant references allow binding to non-lvalues?
The reason is simple because Fortran needs it.
### Move semantics
Traditional C++ has designed the concept of copy/copy for class objects
through copy constructors and assignment operators,
but in order to implement the movement of resources,
The caller must use the method of copying and then destructing first,
otherwise you need to implement the interface of the mobile object yourself.
Imagine moving to move your home directly to your new home instead of
copying everything (rebuy) to your new home.
Throwing away (destroying) all the original things is a very anti-human thing.
Traditional C++ does not distinguish between the concepts of "mobile" and "copy",
resulting in a large amount of data copying, wasting time and space.
The appearance of rvalue references solves the confusion of these two concepts,
for example:
```cpp
#include<iostream>
classA{
public:
int*pointer;
A():pointer(newint(1)){
std::cout<<"construct"<<pointer<<std::endl;
}
A(A&a):pointer(newint(*a.pointer)){
std::cout<<"copy"<<pointer<<std::endl;
}// meaningless object copy
A(A&&a):pointer(a.pointer){
a.pointer=nullptr;
std::cout<<"move"<<pointer<<std::endl;
}
~A(){
std::cout<<"destruct"<<pointer<<std::endl;
deletepointer;
}
};
// avoid compiler optimization
Areturn_rvalue(booltest){
Aa,b;
if(test)returna;// equal to static_cast<A&&>(a);
elsereturnb;// equal to static_cast<A&&>(b);
}
intmain(){
Aobj=return_rvalue(false);
std::cout<<"obj:"<<std::endl;
std::cout<<obj.pointer<<std::endl;
std::cout<<*obj.pointer<<std::endl;
return0;
}
```
In the code above:
1. First construct two `A` objects inside `return_rvalue`, and get the output of the two constructors;
2. After the function returns, it will generate a xvalue, which is referenced by the moving structure of `A` (`A(A&&)`), thus extending the life cycle, and taking the pointer in the rvalue and saving it to `obj`. In the middle, the pointer to the xvalue is set to `nullptr`, which prevents the memory area from being destroyed.
This avoids meaningless copy constructs and enhances performance.
Let's take a look at an example involving a standard library:
```cpp
#include<iostream>// std::cout
#include<utility>// std::move
#include<vector>// std::vector
#include<string>// std::string
intmain(){
std::stringstr="Hello world.";
std::vector<std::string>v;
// use push_back(const T&), copy
v.push_back(str);
// "str: Hello world."
std::cout<<"str: "<<str<<std::endl;
// use push_back(const T&&), no copy
// the string will be moved to vector, and therefore std::move can reduce copy cost
v.push_back(std::move(str));
// str is empty now
std::cout<<"str: "<<str<<std::endl;
return0;
}
```
### Perfect forwarding
As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue.
This creates problems for us to parameterize (pass):
```cpp
#include<iostream>
#include<utility>
voidreference(int&v){
std::cout<<"lvalue reference"<<std::endl;
}
voidreference(int&&v){
std::cout<<"rvalue reference"<<std::endl;
}
template<typenameT>
voidpass(T&&v){
std::cout<<" normal param passing: ";
reference(v);
}
intmain(){
std::cout<<"rvalue pass:"<<std::endl;
pass(1);
std::cout<<"lvalue pass:"<<std::endl;
intl=1;
pass(l);
return0;
}
```
For `pass(1)`, although the value is the rvalue, since `v` is a reference, it is also an lvalue.
Therefore `reference(v)` will call `reference(int&)` and output lvalue.
For `pass(l)`, `l` is an lvalue, why is it successfully passed to `pass(T&&)`?
This is based on the **reference contraction rule**: In traditional C++, we are not able to continue to reference a reference type.
However,
C++ has relaxed this practice with the advent of rvalue references,
resulting in a reference collapse rule that allows us to reference references,
both lvalue and rvalue. But follow the rules below:
| Function parameter type | Argument parameter type | Post-derivation function parameter type |
| :--------: | :--------: | :-------------: |
| T& | lvalue ref | T& |
| T& | rvalue ref | T& |
| T&& | lvalue ref | T& |
| T&& | rvalue ref | T&& |
Therefore, the use of `T&&` in a template function may not be able to make an rvalue reference, and when a lvalue is passed, a reference to this function will be derived as an lvalue.
More precisely, ** no matter what type of reference the template parameter is, the template parameter can be derived as a right reference type** if and only if the argument type is a right reference.
This makes `v` a successful delivery of lvalues.
Perfect forwarding is based on the above rules. The so-called perfect forwarding is to let us pass the parameters,
Keep the original parameter type (lvalue reference keeps lvalue reference, rvalue reference keeps rvalue reference).
To solve this problem, we should use `std::forward` to forward (pass) the parameters:
```cpp
#include<iostream>
#include<utility>
voidreference(int&v){
std::cout<<"lvalue reference"<<std::endl;
}
voidreference(int&&v){
std::cout<<"rvalue reference"<<std::endl;
}
template<typenameT>
voidpass(T&&v){
std::cout<<" normal param passing: ";
reference(v);
std::cout<<" std::move param passing: ";
reference(std::move(v));
std::cout<<" std::forward param passing: ";
reference(std::forward<T>(v));
std::cout<<"static_cast<T&&> param passing: ";
reference(static_cast<T&&>(v));
}
intmain(){
std::cout<<"rvalue pass:"<<std::endl;
pass(1);
std::cout<<"lvalue pass:"<<std::endl;
intl=1;
pass(l);
return0;
}
```
The outputs are:
```
rvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: rvalue reference
static_cast<T&&> param passing: rvalue reference
lvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: lvalue reference
static_cast<T&&> param passing: lvalue reference
```
Regardless of whether the pass parameter is an lvalue or an rvalue, the normal pass argument will forward the argument as an lvalue.
So `std::move` will always accept an lvalue, which forwards the call to `reference(int&&)` to output the rvalue reference.
Only `std::forward` does not cause any extra copies, and ** perfectly forwards ** (passes) the arguments of the function to other functions that are called internally.
`std::forward` is the same as `std::move`, and nothing is done. `std::move` simply converts the lvalue to the rvalue.
`std::forward` is just a simple conversion of the parameters. From the point of view of the phenomenon,
`std::forward<T>(v)` is exactly the same as `static_cast<T&&>(v)`.
Readers may be curious as to why a statement can return values for two types of returns.
Let's take a quick look at the concrete implementation of `std::forward`. `std::forward` contains two overloads:
In this implementation, the function of `std::remove_reference` is to eliminate references in the type.
And `std::is_lvalue_reference` is used to check if the type derivation is correct, in the second implementation of `std::forward`
Check that the received value is indeed an lvalue, which in turn reflects the collapse rule.
When `std::forward` accepts an lvalue, `_Tp` is deduced to the lvalue, so the return value is the lvalue; and when it accepts the rvalue,
`_Tp` is derived as an rvalue reference, and based on the collapse rule, the return value becomes the rvalue of `&& + &&`.
It can be seen that the principle of `std::forward` is to make clever use of the differences in template type derivation.
At this point we can answer the question: Why is `auto&&` the safest way to use looping statements?
Because when `auto` is pushed to a different lvalue and rvalue reference, the collapsed combination with `&&` is perfectly forwarded.
## Conclusion
This chapter introduces the most important runtime enhancements in modern C++, and I believe that all the features mentioned in this section are worth knowing:
Lambda expression
1. Function object container std::function
2. rvalue reference
[Table of Content](./toc.md) | [Previous Chapter](./02-usability.md) | [Next Chapter: Containers](./04-containers.md)
## Further Readings
- [Bjarne Stroustrup, The Design and Evolution of C++](https://www.amazon.com/Design-Evolution-C-Bjarne-Stroustrup/dp/0201543303)
## Licenses
<arel="license"href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><imgalt="Creative Commons License"style="border-width:0"src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png"/></a><br/>This work was written by [Ou Changkun](https://changkun.de) and licensed under a <arel="license"href="http://creativecommons.org/licenses/by-nc-nd/4.0/">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License</a>. The code of this repository is open sourced under the [MIT license](../../LICENSE).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.