Search code examples
c++c++11perfect-forwardingforwarding-referencereference-collapsing

How does std::forward work, especially when passing lvalue/rvalue references?


Possible Duplicate:
What are the main purposes of std::forward and which problems does it solve?

I know what it does and when to use it but I still can't wrap my head around how it works. Please be as detailed as possible and explain when std::forward would be incorrect if it was allowed to use template argument deduction.

Part of my confusion is this: "If it has a name, it's an lvalue" - if that's the case why does std::forward behave differently when I pass thing&& x vs thing& x?


Solution

  • First, let's take a look at what std::forward does according to the standard:

    §20.2.3 [forward] p2

    Returns: static_cast<T&&>(t)

    (Where T is the explicitly specified template parameter and t is the passed argument.)

    Now remember the reference collapsing rules:

    TR   R
    
    T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
    T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
    T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
    T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
    

    (Shamelessly stolen from this answer.)

    And then let's take a look at a class that wants to employ perfect forwarding:

    template<class T>
    struct some_struct{
      T _v;
      template<class U>
      some_struct(U&& v)
        : _v(static_cast<U&&>(v)) {} // perfect forwarding here
                                     // std::forward is just syntactic sugar for this
    };
    

    And now an example invocation:

    int main(){
      some_struct<int> s1(5);
      // in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
      // ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
      // with rvalue reference 'v' bound to rvalue '5'
      // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
      // this just turns 'v' back into an rvalue
      // (named rvalue references, 'v' in this case, are lvalues)
      // huzzah, we forwarded an rvalue to the constructor of '_v'!
    
      // attention, real magic happens here
      int i = 5;
      some_struct<int> s2(i);
      // in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
      // applying the reference collapsing rules yields 'int&' (& + && -> &)
      // ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
      // with lvalue reference 'v' bound to lvalue 'i'
      // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
      // after collapsing rules: 'static_cast<int&>(v)'
      // this is a no-op, 'v' is already 'int&'
      // huzzah, we forwarded an lvalue to the constructor of '_v'!
    }
    

    I hope this step-by-step answer helps you and others understand just how std::forward works.