mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-17 12:44:40 +03:00
book: typo and grammar fixes (#185)
This commit is contained in:
@@ -10,9 +10,9 @@ order: 3
|
||||
|
||||
## 3.1 Lambda Expression
|
||||
|
||||
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 name to call a function. There are actually many, many scenes like this.
|
||||
So anonymous functions are almost standard on modern programming languages.
|
||||
Lambda expressions are one of the most important features in modern C++, and Lambda expressions provide a feature like anonymous functions.
|
||||
Anonymous functions are used when a function is needed, but you don’t want to use a name to call a function. There are many, many scenes like this.
|
||||
So anonymous functions are almost standard in modern programming languages.
|
||||
|
||||
### Basics
|
||||
|
||||
@@ -24,22 +24,22 @@ The basic syntax of a Lambda expression is as follows:
|
||||
}
|
||||
```
|
||||
|
||||
The above grammar rules are well understood except for the things in `[capture list]`,
|
||||
The above grammar rules are well understood except for the things in `[capture list]`,
|
||||
except that the function name of the general function is omitted.
|
||||
The return value is in the form of a `->`
|
||||
The return value is in the form of a `->`
|
||||
(we have already mentioned this in the tail return type earlier in the previous section).
|
||||
|
||||
The so-called capture list can be understood as a type of parameter.
|
||||
The internal function body of a lambda expression cannot use variables outside
|
||||
The so-called capture list can be understood as a type of parameter.
|
||||
The internal function body of a lambda expression cannot use variables outside
|
||||
the body of the function by default.
|
||||
At this time, the capture list can serve to transfer external data.
|
||||
According to the behavior passed,
|
||||
At this time, the capture list can serve to transfer external data.
|
||||
According to the behavior passed,
|
||||
the capture list is also divided into the following types:
|
||||
|
||||
#### 1. Value capture
|
||||
|
||||
Similar to parameter passing, the value capture is based on the fact that
|
||||
the variable can be copied, except that the captured variable is copied
|
||||
Similar to parameter passing, the value capture is based on the fact that
|
||||
the variable can be copied, except that the captured variable is copied
|
||||
when the lambda expression is created, not when it is called:
|
||||
|
||||
```cpp
|
||||
@@ -76,13 +76,13 @@ void lambda_reference_capture() {
|
||||
|
||||
#### 3. Implicit capture
|
||||
|
||||
Manually writing a capture list is sometimes very complicated.
|
||||
This mechanical work can be handled by the compiler.
|
||||
At this point, you can write a `&` or `=` to the compiler to
|
||||
Manually writing a capture list is sometimes very complicated.
|
||||
This mechanical work can be handled by the compiler.
|
||||
At this point, you can write a `&` or `=` to the compiler to
|
||||
declare the reference or value capture.
|
||||
|
||||
To summarize, capture provides the ability for lambda expressions
|
||||
to use external values. The four most common forms of
|
||||
To summarize, capture provides the ability for lambda expressions
|
||||
to use external values. The four most common forms of
|
||||
capture lists can be:
|
||||
|
||||
- \[\] empty capture list
|
||||
@@ -92,16 +92,16 @@ capture lists can be:
|
||||
|
||||
#### 4. Expression capture
|
||||
|
||||
> This section needs to understand the rvalue references and smart pointers that
|
||||
> This section needs to understand the rvalue references and smart pointers that
|
||||
> will be mentioned later.
|
||||
|
||||
The value captures and reference captures mentioned above are variables that have been
|
||||
declared in the outer scope, so these capture methods capture the lvalue
|
||||
The value captures and reference captures mentioned above are variables that have been
|
||||
declared in the outer scope, so these capture methods capture the lvalue
|
||||
and not capture the rvalue.
|
||||
|
||||
C++14 gives us the convenience of allowing the captured members to be initialized
|
||||
C++14 gives us the convenience of allowing the captured members to be initialized
|
||||
with arbitrary expressions, which allows the capture of rvalues.
|
||||
The type of the captured variable being declared is judged according to the expression,
|
||||
The type of the captured variable being declared is judged according to the expression,
|
||||
and the judgment is the same as using `auto`:
|
||||
|
||||
```cpp
|
||||
@@ -114,20 +114,20 @@ void lambda_expression_capture() {
|
||||
}
|
||||
```
|
||||
|
||||
In the above code, `important` is an exclusive pointer that cannot be caught by value capture using `=`.
|
||||
At this time we need to transfer it to the rvalue and
|
||||
In the above code, `important` is an exclusive pointer that cannot be caught by value capture using `=`.
|
||||
At this time we need to transfer it to the rvalue and
|
||||
initialize it in the expression.
|
||||
|
||||
### Generic Lambda
|
||||
|
||||
In the previous section we mentioned that the `auto` keyword cannot be used
|
||||
In the previous section, we mentioned that the `auto` keyword cannot be used
|
||||
in the parameter list because it would conflict with the functionality of the template.
|
||||
But Lambda expressions are not ordinary functions, so Lambda expressions are not templated.
|
||||
This has caused us some trouble: the parameter table cannot be generalized,
|
||||
This has caused us some trouble: the parameter table cannot be generalized,
|
||||
and the parameter table type must be clarified.
|
||||
|
||||
Fortunately, this trouble only exists in C++11, starting with C++14.
|
||||
The formal parameters of the Lambda function can use the `auto` keyword
|
||||
The formal parameters of the Lambda function can use the `auto` keyword
|
||||
to generate generic meanings:
|
||||
|
||||
```cpp
|
||||
@@ -135,7 +135,7 @@ void lambda_generic() {
|
||||
auto generic = [](auto x, auto y) {
|
||||
return x+y;
|
||||
};
|
||||
|
||||
|
||||
std::cout << generic(1, 2) << std::endl;
|
||||
std::cout << generic(1.1, 2.2) << std::endl;
|
||||
}
|
||||
@@ -143,15 +143,15 @@ void lambda_generic() {
|
||||
|
||||
## 3.2 Function Object Wrapper
|
||||
|
||||
Although the features are part of the standard library and not found in runtime,
|
||||
Although the features are part of the standard library and not found in runtime,
|
||||
it enhances the runtime capabilities of the C++ language.
|
||||
This part of the content is also very important, so put it here for introduction.
|
||||
This part of the content is also very important, so put it here for the introduction.
|
||||
|
||||
### `std::function`
|
||||
|
||||
The essence of a Lambda expression is an object of a class type (called a closure type)
|
||||
The essence of a Lambda expression is an object of a class type (called a closure type)
|
||||
that is similar to a function object type (called a closure object).
|
||||
When the capture list of a Lambda expression is empty, the closure object
|
||||
When the capture list of a Lambda expression is empty, the closure object
|
||||
can also be converted to a function pointer value for delivery, for example:
|
||||
|
||||
```cpp
|
||||
@@ -170,20 +170,19 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
The above code gives two different forms of invocation, one is to call Lambda
|
||||
as a function type, and the other is to directly call a Lambda expression.
|
||||
In C++11, these concepts are unified.
|
||||
The type of object that can be called is collectively called the callable type.
|
||||
The above code gives two different forms of invocation, one is to call Lambda
|
||||
as a function type, and the other is to directly call a Lambda expression.
|
||||
In C++11, these concepts are unified.
|
||||
The type of object that can be called is collectively called the callable type.
|
||||
This type is introduced by `std::function`.
|
||||
|
||||
C++11 `std::function` is a generic, polymorphic function wrapper
|
||||
whose instances can store, copy, and call any target entity that can be called.
|
||||
It is also an existing callable to C++. A type-safe package of entities (relatively,
|
||||
the call to a function pointer is not type-safe), in other words,
|
||||
a container of functions. When we have a container for functions,
|
||||
C++11 `std::function` is a generic, polymorphic function wrapper
|
||||
whose instances can store, copy, and call any target entity that can be called.
|
||||
It is also an existing callable to C++. A type-safe package of entities (relatively,
|
||||
the call to a function pointer is not type-safe), in other words,
|
||||
a container of functions. When we have a container for functions,
|
||||
we can more easily handle functions and function pointers as objects. e.g:
|
||||
|
||||
|
||||
```cpp
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -207,10 +206,10 @@ int main() {
|
||||
|
||||
### `std::bind` and `std::placeholder`
|
||||
|
||||
And `std::bind` is used to bind the parameters of the function call.
|
||||
It solves the requirement that we may not always be able to get all the parameters
|
||||
of a function at one time. Through this function, we can Part of the call parameters
|
||||
are bound to the function in advance to become a new object,
|
||||
And `std::bind` is used to bind the parameters of the function call.
|
||||
It solves the requirement that we may not always be able to get all the parameters
|
||||
of a function at one time. Through this function, we can Part of the call parameters
|
||||
are bound to the function in advance to become a new object,
|
||||
and then complete the call after the parameters are complete. e.g:
|
||||
|
||||
```cpp
|
||||
@@ -226,38 +225,38 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
> **Tip:** Note the magic of the `auto` keyword. Sometimes we may not be familiar
|
||||
> **Tip:** Note the magic of the `auto` keyword. Sometimes we may not be familiar
|
||||
> with the return type of a function, but we can circumvent this problem by using `auto`.
|
||||
|
||||
## 3.3 rvalue Reference
|
||||
|
||||
rvalue references are one of the important features introduced by C++11
|
||||
that are synonymous with Lambda expressions. Its introduction solves
|
||||
rvalue references are one of the important features introduced by C++11
|
||||
that are synonymous with Lambda expressions. Its introduction solves
|
||||
a large number of historical issues in C++.
|
||||
Eliminating extra overhead such as `std::vector`, `std::string`,
|
||||
Eliminating extra overhead such as `std::vector`, `std::string`,
|
||||
and making the function object container `std::function` possible.
|
||||
|
||||
### lvalue, rvalue, prvalue, xvalue
|
||||
|
||||
To understand what the rvalue reference is all about, you must have a clear
|
||||
To understand what the rvalue reference is all about, you must have a clear
|
||||
understanding of the lvalue and the rvalue.
|
||||
|
||||
**lvalue, left value**, as the name implies, is the value to the left of the assignment
|
||||
symbol. To be precise, an lvalue is a persistent object that still exists after
|
||||
**lvalue, left value**, as the name implies, is the value to the left of the assignment
|
||||
symbol. To be precise, an lvalue is a persistent object that still exists after
|
||||
an expression (not necessarily an assignment expression).
|
||||
|
||||
**Rvalue, right value**, the value on the right refers to the temporary object
|
||||
**Rvalue, right value**, the value on the right refers to the temporary object
|
||||
that no longer exists after the expression ends.
|
||||
|
||||
In C++11, in order to introduce powerful rvalue references,
|
||||
the concept of rvalue values is further divided into:
|
||||
In C++11, in order to introduce powerful rvalue references,
|
||||
the concept of rvalue values is further divided into:
|
||||
prvalue, and xvalue.
|
||||
|
||||
**pvalue, pure rvalue**, purely rvalue, either purely literal,
|
||||
such as `10`, `true`; either the result of the evaluation is equivalent to
|
||||
a literal or anonymous temporary object, for example `1+2`.
|
||||
Temporary variables returned by non-references, temporary variables generated
|
||||
by operation expressions, original literals, and Lambda expressions
|
||||
**pvalue, pure rvalue**, purely rvalue, either purely literal,
|
||||
such as `10`, `true`; either the result of the evaluation is equivalent to
|
||||
a literal or anonymous temporary object, for example `1+2`.
|
||||
Temporary variables returned by non-references, temporary variables generated
|
||||
by operation expressions, original literals, and Lambda expressions
|
||||
are all pure rvalue values.
|
||||
|
||||
Note that a string literal became rvalue in a class, and remains an lvalue in other cases (e.g., in a function):
|
||||
@@ -276,11 +275,11 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
**xvalue, expiring value** is the concept proposed by C++11 to introduce
|
||||
rvalue references (so in traditional C++, pure rvalue and rvalue are the same concept),
|
||||
**xvalue, expiring value** is the concept proposed by C++11 to introduce
|
||||
rvalue references (so in traditional C++, pure rvalue and rvalue are the same concepts),
|
||||
a value that is destroyed but can be moved.
|
||||
|
||||
It would be a little hard to understand the xvalue,
|
||||
It would be a little hard to understand the xvalue,
|
||||
let's look at the code like this:
|
||||
|
||||
```cpp
|
||||
@@ -292,33 +291,33 @@ std::vector<int> foo() {
|
||||
std::vector<int> v = foo();
|
||||
```
|
||||
|
||||
In such code, as far as the traditional understanding is concerned,
|
||||
the return value `temp` of the function `foo` is internally created
|
||||
and then assigned to `v`, whereas when `v` gets this object, the entire `temp` is copied.
|
||||
And then destroy `temp`, if this `temp` is very large, this will cause a lot of extra
|
||||
overhead (this is the problem that traditional C++ has been criticized for).
|
||||
In the last line, `v` is the lvalue, and the value returned by `foo()` is
|
||||
the rvalue (which is also a pure rvalue).
|
||||
However, `v` can be caught by other variables, and the return value generated
|
||||
by `foo()` is used as a temporary value. Once copied by `v`,
|
||||
it will be destroyed immediately, and cannot be obtained or modified.
|
||||
The xvalue defines an behavior in which temporary values can be identified
|
||||
while being able to be moved.
|
||||
In such code, as far as the traditional understanding is concerned,
|
||||
the return value `temp` of the function `foo` is internally created
|
||||
and then assigned to `v`, whereas when `v` gets this object, the entire `temp` is copied.
|
||||
And then destroy `temp`, if this `temp` is very large, this will cause a lot of extra
|
||||
overhead (this is the problem that traditional C++ has been criticized for).
|
||||
In the last line, `v` is the lvalue, and the value returned by `foo()` is
|
||||
the rvalue (which is also a pure rvalue).
|
||||
|
||||
After C++11, the compiler did some work for us, where the lvalue `temp`
|
||||
is subjected to this implicit rvalue conversion,
|
||||
equivalent to `static_cast<std::vector<int> &&>(temp)`,
|
||||
where `v` here moves the value returned by `foo` locally.
|
||||
However, `v` can be caught by other variables, and the return value generated by `foo()`
|
||||
is used as a temporary value. Once copied by `v`, it will be destroyed immediately, and
|
||||
cannot be obtained or modified. The xvalue defines behavior in which temporary values can be
|
||||
identified while being able to be moved.
|
||||
|
||||
After C++11, the compiler did some work for us, where the lvalue `temp`
|
||||
is subjected to this implicit rvalue conversion,
|
||||
equivalent to `static_cast<std::vector<int> &&>(temp)`,
|
||||
where `v` here moves the value returned by `foo` locally.
|
||||
This is the move semantics we will mention later.
|
||||
|
||||
### rvalue reference and lvalue reference
|
||||
|
||||
To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`,
|
||||
To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`,
|
||||
where `T` is the type.
|
||||
The statement of the rvalue reference extends the lifecycle of this temporary value,
|
||||
The statement of the rvalue reference extends the lifecycle of this temporary value,
|
||||
and as long as the variable is alive, the xvalue will continue to survive.
|
||||
|
||||
C++11 provides the `std::move` method to unconditionally convert
|
||||
C++11 provides the `std::move` method to unconditionally convert
|
||||
lvalue parameters to rvalues.
|
||||
With it we can easily get a rvalue temporary object, for example:
|
||||
|
||||
@@ -339,25 +338,25 @@ int main()
|
||||
// std::string&& r1 = s1; // illegal, rvalue can't ref to lvalue
|
||||
std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue
|
||||
std::cout << rv1 << std::endl; // string,
|
||||
|
||||
|
||||
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle
|
||||
// lv2 += "Test"; // illegal, const ref can't be modified
|
||||
std::cout << lv2 << std::endl; // string,string
|
||||
|
||||
|
||||
std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle
|
||||
rv2 += "string"; // legal, non-const reference can be modified
|
||||
std::cout << rv2 << std::endl; // string,string,string,string
|
||||
|
||||
|
||||
reference(rv2); // output: lvalue
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
`rv2` refers to an rvalue, but since it is a reference,
|
||||
`rv2` refers to an rvalue, but since it is a reference,
|
||||
`rv2` is still an lvalue.
|
||||
|
||||
Note that there is a very interesting historical issue here,
|
||||
Note that there is a very interesting historical issue here,
|
||||
let's look at the following code:
|
||||
|
||||
```cpp
|
||||
@@ -371,7 +370,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
The first question, why not allow non-linear references to bind to non-lvalues?
|
||||
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
|
||||
@@ -384,28 +383,28 @@ void foo() {
|
||||
}
|
||||
```
|
||||
|
||||
Since `int&` can't reference a parameter of type `double`,
|
||||
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,
|
||||
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 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 your home directly to your new home instead of
|
||||
Traditional C++ has designed the concept of copy/copy for class objects
|
||||
through copy constructors and assignment operators,
|
||||
but 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 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",
|
||||
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,
|
||||
The appearance of rvalue references solves the confusion of these two concepts,
|
||||
for example:
|
||||
|
||||
```cpp
|
||||
@@ -413,19 +412,19 @@ for example:
|
||||
class A {
|
||||
public:
|
||||
int *pointer;
|
||||
A():pointer(new int(1)) {
|
||||
std::cout << "construct" << pointer << std::endl;
|
||||
A():pointer(new int(1)) {
|
||||
std::cout << "construct" << pointer << std::endl;
|
||||
}
|
||||
A(A& a):pointer(new int(*a.pointer)) {
|
||||
std::cout << "copy" << pointer << std::endl;
|
||||
A(A& a):pointer(new int(*a.pointer)) {
|
||||
std::cout << "copy" << pointer << std::endl;
|
||||
} // meaningless object copy
|
||||
A(A&& a):pointer(a.pointer) {
|
||||
A(A&& a):pointer(a.pointer) {
|
||||
a.pointer = nullptr;
|
||||
std::cout << "move" << pointer << std::endl;
|
||||
std::cout << "move" << pointer << std::endl;
|
||||
}
|
||||
~A(){
|
||||
std::cout << "destruct" << pointer << std::endl;
|
||||
delete pointer;
|
||||
~A(){
|
||||
std::cout << "destruct" << pointer << std::endl;
|
||||
delete pointer;
|
||||
}
|
||||
};
|
||||
// avoid compiler optimization
|
||||
@@ -448,7 +447,7 @@ 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.
|
||||
This avoids meaningless copy constructs and enhances performance.
|
||||
Let's take a look at an example involving a standard library:
|
||||
|
||||
```cpp
|
||||
@@ -458,28 +457,28 @@ Let's take a look at an example involving a standard library:
|
||||
#include <string> // std::string
|
||||
|
||||
int main() {
|
||||
|
||||
|
||||
std::string str = "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;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Perfect forwarding
|
||||
|
||||
As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue.
|
||||
As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue.
|
||||
This creates problems for us to parameterize (pass):
|
||||
|
||||
```cpp
|
||||
@@ -513,21 +512,21 @@ 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 collapsing 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,
|
||||
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&& |
|
||||
| :---------------------: | :---------------------: | :-------------------------------------: |
|
||||
| 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.
|
||||
This makes `v` 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).
|
||||
@@ -556,11 +555,11 @@ void pass(T&& v) {
|
||||
int main() {
|
||||
std::cout << "rvalue pass:" << std::endl;
|
||||
pass(1);
|
||||
|
||||
|
||||
std::cout << "lvalue pass:" << std::endl;
|
||||
int l = 1;
|
||||
pass(l);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -583,11 +582,11 @@ 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.
|
||||
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)`.
|
||||
`std::forward<T>(v)` is 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:
|
||||
@@ -614,7 +613,7 @@ When `std::forward` accepts an lvalue, `_Tp` is deduced to the lvalue, so the re
|
||||
`_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?
|
||||
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
|
||||
@@ -622,6 +621,7 @@ Because when `auto` is pushed to a different lvalue and rvalue reference, the co
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user