14

Consider:

void g(int&);
void g(int&&);

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

int main()
{
    f(10);
}

Since the id-expression x is an lvalue, and std::forward has overloads for lvalues and rvalues, why doesn't the call bind to the overload of std::forward that takes an lvalue?

template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
Praetorian
  • 106,671
  • 19
  • 240
  • 328
template boy
  • 10,230
  • 8
  • 61
  • 97
  • 1
    For a "long" answer, see Thomas Becker's excellent article on rvalue references: http://thbecker.net/articles/rvalue_references/section_01.html. Your question is discussed in section 7 and 8. – andreee Nov 16 '18 at 12:57

3 Answers3

19

It does bind to the overload of std::forward taking an lvalue:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

It binds with T == int. This function is specified to return:

static_cast<T&&>(t)

Because the T in f deduced to int. So this overload casts the lvalue int to xvalue with:

static_cast<int&&>(t)

Thus calling the g(int&&) overload.

In summary, the lvalue overload of std::forward may cast its argument to either lvalue or rvalue, depending upon the type of T that it is called with.

The rvalue overload of std::forward can only cast to rvalue. If you try to call that overload and cast to lvalue, the program is ill-formed (a compile-time error is required).

So overload 1:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

catches lvalues.

Overload 2:

template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;

catches rvalues (which is xvalues and prvalues).

Overload 1 can cast its lvalue argument to lvalue or xvalue (the latter which will be interpreted as an rvalue for overload resolution purposes).

Overload 2 can can cast its rvalue argument only to an xvalue (which will be interpreted as an rvalue for overload resolution purposes).

Overload 2 is for the case labeled "B. Should forward an rvalue as an rvalue" in N2951. In a nutshell this case enables:

std::forward<T>(u.get());

where you are unsure if u.get() returns an lvalue or rvalue, but either way if T is not an lvalue reference type, you want to move the returned value. But you don't use std::move because if T is an lvalue reference type, you don't want to move from the return.

I know this sounds a bit contrived. However N2951 went to significant trouble to set up motivating use cases for how std::forward should behave with all combinations of the explicitly supplied template parameter, and the implicitly supplied expression category of the ordinary parameter.

It isn't an easy read, but the rationale for each combination of template and ordinary parameters to std::forward is in N2951. At the time this was controversial on the committee, and not an easy sell.

The final form of std::forward is not exactly what N2951 proposed. However it does pass all six tests presented in N2951.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
9

why doesn't the call bind to the overload of std::forward that takes an lvalue?

It does exactly that, but std::forward does not deduce its template argument, you tell it what type it is, and that's where the magic occurs. You're passing a prvalue to f() so f() deduces [T = int]. It then calls the lvalue overload of forward, and due to reference collapsing both the return type and static_cast<T&&> that happens within forward will be of type int&&, thus calling the void g(int&&) overload.

If you were to pass an lvalue to f()

int x = 0;
f(x);

f() deduces [T = int&], again the same lvalue overload of forward gets called, but this time the return type and static_cast<T&&> are both int&, again because of the reference collapsing rules. This will then call the void g(int&) overload instead.

Live demo


Howard already has a good answer for your question about why the rvalue overload of forward is required, but I'll add a contrived example that shows the two versions in action.

The basic idea behind the two overloads is that the rvalue overload will be invoked in cases where the result of the expression you pass to forward yields an rvalue (prvalue or xvalue).

Say you have a type foo that has a ref-qualified pair of get() member function overloads. The one with the && qualifier returns an int while the other returns int&.

struct foo
{
    int i = 42;
    int  get() && { return i; }
    int& get() &  { return i; }
};

And say f() invokes the get() member function on whatever was passed to it, and forwards that on to g()

template<class T>
auto f(T&& t)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
    g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}

foo foo1;
f(foo1);   // calls lvalue overload of forward for both calls to forward

f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
                    // but calls rvalue overload for outer call to forward

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    What is the rvalue overload for? – template boy Mar 19 '15 at 02:06
  • 1
    You wouldn't be able to call `forward(std::move(t))` without the second overload, because rvalue references are not auto-convertible to lvalue references. In the inner _forward_ (i.e. `forward(t)`) the argument `t` is lvalue and the first overload is chosen. In the outer _forward_ (i.e. `forward(...)`) the argument (the value returned by the inner _forward_) is rvalue reference and the second overload is chosen. Ok, I understand it now. – anton_rh Sep 09 '19 at 14:40
2

How does std::forward receive the correct argument?

For perfect forwarding,as your code:

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

Note this:std::forward requires both a function argument and a template type argument. The template parameter T will encode whether the argument passed to param was an lvalue or an rvalue and then forward use it. In this case,not matter x refer what ,x itself is an lvalue,choice overload 1 ,and x is forwarding(universal) reference parameters.Rules as follow:

  1. if f the argument's expr is lvalue ,Both x and T will lvalue reference type
  2. if f the argument's expr is rvalue ,T will be non-reference type.

For example use gcc source code :

  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); } //overload 1


  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);
    }// overload 2
  • if pass function f lvalue of type is string or string&,for forward function _Tp will be string&,__t will be string & & is equal to string&,_Tp&& will be string& && is equal to string&.(reference collapsing)
  • if pass function f rvalue of type is string or string&&,for forward function _Tp will be string,__t will be string&,_Tp&& will be string&&.

What the function do?

  • if rvalue to rvalue(Overload 2) or lvalue to lvalue(Overload 1) ,nothing to do,just return.
  • If you're not careful or something else, lead rvalue to lvalue, gives you a compilation error by static_assert.(Overload 2)
  • If you do lvalue to rvalue(Overload 1),it is same with std::move,but not convenience.

So just as Scott Meyers say:

Given that both std::move and std::forward boil down to casts, the only difference being that std::move always casts, while std::forward only sometimes does, you might ask whether we can dispense with std::move and just use std::forward everywhere. From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.

What is the rvalue overload for?

I'm not sure, it should be used where you really need it.Such as Howard Hinnant says:

Overload 2 is for the case labeled "B. Should forward an rvalue as an rvalue" in N2951. In a nutshell this case enables:

std::forward<T>(u.get());

where you are unsure if u.get() returns an lvalue or rvalue, but either way if T is not an lvalue reference type, you want to move the returned value. But you don't use std::move because if T is an lvalue reference type, you don't want to move from the return.

In a word,it allow us use T's type to decide whether move or not.

Ron Tang
  • 1,532
  • 12
  • 20