Are there any use cases for std::forward with a prvalue?

前端 未结 3 1137
醉梦人生
醉梦人生 2021-02-01 17:46

The most common usage of std::forward is to, well, perfect forward a forwarding (universal) reference, like

template
void f(T&         


        
3条回答
  •  暖寄归人
    2021-02-01 17:55

    I stared at this question before, read Howard Hinnant's link, couldn't fully grok it after an hour of thinking. Now I was looking and got the answer in five minutes. (Edit: got the answer is too generous, as Hinnant's link had the answer. I meant that I understood, and was able to explain it in a simpler way, which hopefully someone will find helpful).

    Basically, this allows you to be generic in certain kinds of situations depending on the typed that's passed in. Consider this code:

    #include 
    #include 
    #include 
    using namespace std;
    
    class GoodBye
    {
      double b;
     public:
      GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
      GoodBye( const double& a):b(a){ std::cerr << "copy"; }
    };
    
    struct Hello {
      double m_x;
    
      double & get()  { return m_x; }
    };
    
    int main()
    {
      Hello h;
      GoodBye a(std::forward(std::move(h).get()));
      return 0;
    }
    

    This code prints "move". What's interesting is that if I remove the std::forward, it prints copy. This, for me, is hard to wrap my mind around, but let's accept it and move on. (Edit: I suppose this happens because get will return a lvalue reference to an rvalue. Such an entity decays into an lvalue, but std::forward will cast it into an rvalue, just as in the common use of forward. Still feels unintuitive though).

    Now, let's imagine another class:

    struct Hello2 {
      double m_x;
    
      double & get() & { return m_x; }
      double && get() && { return std::move(m_x); }
    };
    

    Suppose in the code in main, h was an instance of Hello2. Now, we no longer need std::forward, because the call to std::move(h).get() returns an rvalue. However, suppose the code is generic:

    template 
    void func(T && h) {
      GoodBye a(std::forward(std::forward(h).get()));
    }
    

    Now when we call func, we'd like it to work properly with both Hello and Hello2, i.e. we'd like to trigger a move. That only happens for an rvalue of Hello if we include the outer std::forward, so we need it. But... We got to the punchline. When we pass an rvalue of Hello2 to this function, the rvalue overload of get() will already return an rvalue double, so std::forward is actually accepting an rvalue. So if it didn't, you wouldn't be able to write fully generic code as above.

    Damn.

提交回复
热议问题