std::forward memo

Friends, you will definitely come into contact with std::move, std::forward after initially learning the movement operation of C++, but do you really understand the specific meaning of std::move and std::forward?

This article only involves part of the knowledge explanation of std::forward

Requirements for using std::forward: template forwarding parameters (perfect forwarding)

The so-called forwarding is to call another function in the template function, and the parameters passed by the external function can maintain the left and right value attributes and pass it to the internal function.
It is roughly in the following form:

template<typename fun, typename a, typename b>
void exec(fun f, a tmpa, b tmpb) {
    
    
    f(a,b);
}

The following are several bad or incorrect forwarding methods:
##Original copy forwarding

template<typename fun, typename a, typename b>
exec(fun f, a tmpa, b tmpb);

The exec template function in Figure 1 below passes a copy of a and b type objects (for example, a string is passed in, although an int is passed here), which is inefficient.

figure 1

template<typename fun, typename a, typename b>
void exec(fun f, a tmpa, b tmpb) {
    
    
    f(a,b);
}

int tmp(int a, int b) {
    
    
    cout << a * b << endl;
}

int main() {
    
    
    int a = 5;
    int b = 6;
    exec(tmp, a, b);
}

##External function lvalue forwarding

template<typename fun, typename a, typename b>
exec(fun f, a& tmpa, b &tmpb);

The exec template function in Figure 2 below passes in the lvalue references of type a and b objects, and exec in the main function cannot directly pass in the rvalue of the number 6.

figure 2

template<typename fun, typename a, typename b>
void exec(fun f, a& tmpa, b& tmpb) {
    
    
	f(a,b);
}

int tmp(int a, int b) {
    
    
    cout << a * b << endl;
}

int main() {
    
    
    int a = 5;
    int b = 6;
    exec(tmp, a, 6);
}

##External function lvalue forwarding, internal function contains rvalue parameters

template<typename fun, typename a, typename b>
exec(fun f, a& tmpa, b &tmpb);

Figure 3 below, when the parameter of tmp is an rvalue reference, since tmpb in exec is an lvalue, tmp cannot be given as a parameter

image 3

template<typename fun, typename a, typename b>
void exec(fun f, a& tmpa, b& tmpb) {
    
    
  	f(a,b);
}

int tmp(int &a, int&& b) {
    
    
    cout << a * b << endl;
}

int main() {
    
    
    int a = 5;
    int b = 6;
    exec(tmp, a, b);
}

##External function rvalue reference forwarding

template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb);

Figure 4 below has solved the problem of exec passing rvalues, but the tmpb passed to tmp is still an lvalue (the rvalue is referenced as an lvalue). If we directly use
std::move(tmpb) to pass the rvalue to the tmp function, It is indeed feasible at this time, but we are not sure whether the tmpa and tmpb passed by the outside world to exec are left-value or right-value, and the forwarding we need needs to maintain the left and right value attributes of the parameters. At this time, we have no good way To deal with the way template functions call internal functions.

Figure 4

template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb) {
    
    
    f(a,std::move(tmpb));
}
int tmp(int &a, int &&b) {
    
    
    cout << a * b << endl;
}

int main() {
    
    
    int a = 5;
    int b = 6;
    exec(tmp, a, 6);
}

As a unified approach, we need to keep the lvalue passed to exec and send it to tmp, and convert the lvalue parameter that holds the rvalue of exec, such as tmpb, into an rvalue and send it to tmp, but this is not good by itself. Implementation, but the standard library cleverly implements this process std::forward<T>(), the following figure 5 template function only needs a unified call functionf(std::forward<a>(tmpa), std::forward<b>(tmpb));

Figure 5

template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb) {
    
    
    f(std::forward<a>(tmpa), std::forward<b>(tmpb));

}

int tmp(int &a, int &&b) {
    
    
    cout << a * b << endl;
}

int main() {
    
    
    int a = 5;
    int b = 6;
    exec(tmp, a, 6);
}

Everyone will be very curious about the implementation principle of forward.
As shown in Figure 6
std::forward source code

  /**
   *  @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&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    {
    
     return static_cast<_Tp&&>(__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&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
    
    
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }

Whether the incoming _Tp is int or int& or int&&
std::remove_reference<_Tp>::typewill be int
std::remove_reference<_Tp>::type&will be int&,
std::remove_reference<_Tp>::type&&will be int&&

The parameters tmpa and tmpb of exec are both lvalues ​​(the rvalue references are lvalues). At this time, using forward will only call the lvalue version of the forward function, tempa passes int&, tempb passes int&&, and is folded by reference. The type of a is deduced as int&, and b is deduced as int,
so
forward(tmpa) means forward<int&>(int&) In the
forward function _Tp=int&,_Tp&&=int&,
so it return static_cast<_Tp&&>(__t)returns int&, which is an lvalue

forward<b>(tmpb)That is, forward<int>(int&) forward函数内_Tp=int&,_Tp&&=int&, 因此return static_cast<_Tp&&>(__t)` returns int&&, which is an rvalue

Of course, when the forward argument is an rvalue, another overloaded function will be called, and finally the rvalue will be returned.

So it can be said that forward must be matched with template +&& parameters to achieve perfect forwarding

For example, the smart pointer function std::make_shared uses the perfect forwarding model. The mystery of the source code in C++ is really endless.

  template<typename _Tp, typename... _Args>
    inline shared_ptr<_Tp>
    make_shared(_Args&&... __args)
    {
    
    
      typedef typename std::remove_const<_Tp>::type _Tp_nc;
      return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
				       std::forward<_Args>(__args)...);
    }

Guess you like

Origin blog.csdn.net/adlatereturn/article/details/108732422
Recommended