Reference folding, universal reference, rvalue reference, move, perfect forwarding

Hello everyone, my name is Xu Jintong, and my personal blog address is www.xujintong.com. I usually record the knowledge gained in the process of learning computers, as well as the daily tossing experience. Everyone is welcome to visit.

I will write a note here to record the problems I encountered when writing STL source code.

1. Reference folding

Reference folding indicates what type will be generated when a value is referenced multiple times (Only during template deduction).

T & amp; & amp; folds into T & amp;
T & amp; & amp; & amp; folds to T & amp;
T & amp; & amp; & amp; folds to T & amp;
T & amp; & amp; & amp; & amp; folds into T & amp; & amp;

In fact, to sum up, it is T & amp; & amp; only when both are rvalue references, that is, T & amp; & amp; & amp; & amp; is folded into T & amp; & amp ;, others are converted into lvalue references T & amp;.

2. lvalue reference

An lvalue reference is an alias that binds one variable to another variable.

int a = 10;
int & amp;b = a; // lvalue reference
const int & amp;c = 10; // constant lvalue reference

b = 20;
std::cout << a << std::endl; // 20

Modifying the reference variable will affect the value of the original variable, but const int & amp; cannot modify the value of the original object (the int in the middle can be replaced with other types).
Generally speaking, when a function passes parameters, it copies the parameters, that is, copies the variable to a temporary variable, then passes the temporary variable into the function, and finally destroys the temporary variable. This involves memory copying, which takes a long time. However, if you use an lvalue reference when passing parameters, it will not involve a copy of the memory, because it is an alias, which is equivalent to directly modifying the original variable.

void left_value(int & amp; x) {
    x = 200;
}

int main()
{
    int value = 10;
    left_value(value);
    std::cout << value << std::endl; // 200
    return 0;
}

Of course, if it is const & amp;, although the memory copy process is not involved, the original variable cannot be modified.

void left_value(const int & amp; x) {
    x = 200; // error: assignment of read-only reference 'x'
}

int main()
{
    int value = 10;
    left_value(value);
    std::cout << value << std::endl; // 200
    return 0;
}

When the function passes parameters here, why can int match int & amp; or const int & amp;?

When you pass an int type to an int & amp; parameter, C++ performs a type conversion, converting int implicitly Convert to int & amp;, the other one is the same. Note: int, int & amp;, const int & amp; have the same priority when matching functions, and ambiguity errors will occur if they appear at the same time.
Implicit conversion is very common when passing parameters to functions.

void left_value(int num) {<!-- -->
    std::cout << num << std::endl;
}

int main()
{<!-- -->
   float a = 1.5;
   left_value(a); // 1
   return 0;
}

When passed in, the float type is implicitly converted to the int type.

But using function overloading can solve this problem.

void left_value(int num) {<!-- -->
    std::cout << num << std::endl;
}

void left_value(float num) {<!-- -->
    std::cout << num << std::endl;
}

int main()
{<!-- -->
   float a = 1.5;
   left_value(a);
   return 0;
}

The most appropriate function will be called. If not, implicit type conversion can only be performed.

3. Rvalue reference

Rvalue references are a new feature of C++11 references. That is, an lvalue reference adds an alias to a variable, while an rvalue reference binds a variable to a temporary value. The life cycle of the temporary variable is bound to the life cycle of the new lvalue, and the original temporary variable is destroyed.

int & amp; & amp;d = 10; // rvalue reference

d = 200; // At this time d is a variable and an lvalue

Reference is a special type in C++ because its value type is different from the variable type. The value types of lvalue/rvalue reference variables are all lvalues, not lvalue references or rvalue references. . The meaning of this sentence is very important. To put it simply, look at the above code. At first, d is bound to 10, and then d is assigned a value. After int & amp; & amp;d=10 , every time d is used, d is used as an lvalue. An rvalue reference variable becomes an lvalue when used. It no longer carries the information that it is a right reference, but is just an lvalue.

Rvalue references are also used to improve performance.

vector<string> v;
string s = "teststring";
v.push_back(s);

The above code uses a temporary variable. If we do nothing, what will happen? First, we will create a temporary string, then copy the temporary string to the memory of v, and then destroy the temporary string. Memory is opened and destroyed in the middle, and there is no problem with performance on general data. If there is a lot of data, performance will be seriously degraded.

But if we use rvalue reference, the following code.

vector<string> v;
string s = "teststring";
v.push_back(std::move(s));

The std::move() here is to force an lvalue into an rvalue. I will talk about it below, but now you know that you can force an lvalue into an rvalue.
The intermediate process becomes to create a temporary string and then directly mount the temporary string to v. Is there a lot of processes eliminated in the middle, and the performance is greatly improved?

4. Universal quotation

In template programming, sometimes you need to pass in lvalues and rvalue references.
The following can only pass in one lvalue. If you pass in an rvalue, an error will be reported.

template <typename T>
void func(T & amp; value) {<!-- -->
    std::cout << "Function call" << std::endl;
}

int main()
{<!-- -->
    int value = 10;
    func(value);
    func(10); // Report error
    return 0;
}

This is when our universal reference T & amp; & amp; is used.

template <typename T>
void func(T & amp; & amp; value) {<!-- -->
    std::cout << "Function call" << std::endl;
}

int main()
{<!-- -->
    int value = 10;
    func(value);
    func(10);

    return 0;
}

explain:
Both lvalue references and rvalue references can be passed in at the same time.
If it is an lvalue, T will first be deduced into T & amp;, and then reference folding will occur. If it is an rvalue reference, T will be deduced into T & amp; & amp;.
For example, the value in func(value) is an lvalue. After it is passed in, T will be deduced into T & amp;, and then the following & amp; & amp; will be folded. will become T&.
The 10 in func(10) is an rvalue, and T will be deduced into T & amp; & amp;, and then reference folding will occur with the following & amp; & amp;, and finally it will become T&&.
**T & amp; & amp; only in

5. move

std::moveIt actually does nothing, it just forces the lvalue to be converted into an rvalue.
show you the code.

template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type & amp; & amp;
    move(_Tp & amp; & amp; __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type & amp; & amp;>(__t); }

The std::remove_reference<_Tp> here is like removing the reference of a variable. If _Tp is int & amp; & amp;, the final returned type is an int.

The source code of std::remove_reference<_Tp> is as follows.

/// remove_reference
  template<typename_Tp>
    struct remove_reference
    {<!-- --> typedef _Tp type; };

  template<typename_Tp>
    struct remove_reference<_Tp &>
    {<!-- --> typedef _Tp type; };

  template<typename_Tp>
    struct remove_reference<_Tp & amp; & amp;>
    {<!-- --> typedef _Tp type; };

  template<typename _Tp, bool = __is_referenceable<_Tp>::value>
    struct __add_lvalue_reference_helper
    {<!-- --> typedef _Tp type; };

  template<typename_Tp>
    struct __add_lvalue_reference_helper<_Tp, true>
    {<!-- --> typedef _Tp & amp; type; };

6. Perfect forwarding

Perfect forwarding is used in conjunction with universal references. The lvalue and rvalue parameters are passed in through universal references, and then this attribute is retained through perfect forwarding and forwarded to the corresponding overloaded function.
show the code.

/*
    Overloading of function templates
*/
template <typename T>
void to_forward(T & amp; value) {<!-- -->
    std::cout << "The lvalue function called" << std::endl;
}

template <typename T>
void to_forward(T & amp; & amp; value) {<!-- -->
    std::cout << "The rvalue function called" << std::endl;
}

template <typename T>
void func(T & amp; & amp; arg) {<!-- -->

    to_forward(arg); // Call the left value function
}

int main(){<!-- -->
    int value = 10;
    func(std::move(value));
}

In this code, the to_forward function in the func function will eventually call to_forward(T & amp; value), although an rvalue is passed in, and an rvalue reference variable In use, it becomes an lvalue. It no longer carries information that it is a right reference. It is just an lvalue, so the overloaded function of the lvalue is called here.

First, show the source code of std::forward.

/**
   * @brief Forward an lvalue.
   * @return The parameter cast to the specified type.
   *
   * This function is used to implement "perfect forwarding".
   */
  template<typename_Tp>
    constexpr _Tp & amp; & amp;
    forward(typename std::remove_reference<_Tp>::type & amp; __t) noexcept
    {<!-- --> return static_cast<_Tp & amp; & amp;>(__t); }

  /**
   * @brief Forward an rvalue.
   * @return The parameter cast to the specified type.
   *
   * This function is used to implement "perfect forwarding".
   */
  template<typename_Tp>
    constexpr _Tp & amp; & amp;
    forward(typename std::remove_reference<_Tp>::type & amp; & amp; __t) noexcept
    {<!-- -->
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
      return static_cast<_Tp & amp; & amp;>(__t);
    }

There are two overloaded functions here, one corresponding to lvalues and one corresponding to rvalues.
forward(typename std::remove_reference<_Tp>::type & amp; __t)This is to first remove the reference from the type of _t, and then add an & amp;, that is, the last is an lvalue type.
Then the following forward(typename std::remove_reference<_Tp>::type & amp; & amp; __t) must be an rvalue.

Reference folding is also used here, and the return method is the same, both are return static_cast<_Tp & amp; & amp;>(__t), if _Tp is & amp;, then in Folding with the following & amp; & amp; is an lvalue; if _Tp is & amp; & amp;, then folding with the following & amp; & amp; will still be & amp; & amp;.

std::move and std::forward actually do nothing, they just force the type to be converted.

Look at the code below, this is correct. We retain the lvalue or rvalue property through perfect forwarding, and then pass it to another function.

#include <iostream>
#include <utility>

/* Perfect forwarding + universal reference + std::move */

/*
    Overloading of function templates
*/
template <typename T>
void to_forward(T & amp; value) {<!-- -->
    std::cout << "The lvalue function called" << std::endl;
}

template <typename T>
void to_forward(T & amp; & amp; value) {<!-- -->
    std::cout << "The rvalue function called" << std::endl;
}

/*
    Universal quote:
        You can pass in both lvalue references and rvalue references at the same time
        If it is an lvalue, T will first be deduced into T & amp;, and then reference folding will occur.
        If the investment is an rvalue reference, it will do nothing.
    Utilize perfect forwarding std::forward:
        First, you can pass in lvalue references and rvalue references through universal references.
        Then through perfect forwarding (which can retain the attribute of whether it is an lvalue reference or an rvalue reference when passed in, and then forward it to the corresponding function overload
*/
template <typename T>
void func(T & amp; & amp; arg) {<!-- -->
    // Preserve the properties of lvalue and rvalue
    to_forward(std::forward<T>(arg));
}

int main() {<!-- -->

    int value = 10;

    int & value_l_refernce = value;

    func(value); // lvalue reference function called

    func(value_l_refernce); // Called lvalue reference function

    func(100); // rvalue reference function called

    int & amp; & amp; value_r_refernce = 30;
    // After the rvalue reference is used, subsequent calls to this variable will be used as an lvalue
    func(value_r_refernce); // **The lvalue reference of the call**

    func(std::move(value)); // rvalue reference of the call

    return 0;
}