2

In the c++ std type_traits file below the first overloaded function, the comment says:

forward an lvalue as either an lvalue or an rvalue

However the return value is just an rvalue reference, I wonder how it could be either an lavlue or an rvalue? Does it mean the returned value is a universal reference? If so what decides how to deduce it to an lvalue reference or an rvalue reference?

Also the second overload returns exactly the same thing, why does it say in the comment only forwarding as a rvalue reference without lvalue reference?

template <class _Ty>
    _NODISCARD constexpr _Ty&& forward(
        remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
        return static_cast<_Ty&&>(_Arg);
    }
    
    template <class _Ty>
    _NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
        static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
        return static_cast<_Ty&&>(_Arg);
    }
user438383
  • 5,716
  • 8
  • 28
  • 43
Anning Wuwang
  • 333
  • 1
  • 8
  • Does this answer your question? [How does std::forward work?](https://stackoverflow.com/questions/8526598/how-does-stdforward-work) – Alex Vask Jun 08 '22 at 12:06
  • I'd recommend looking at the Xeo's answer @ the link above, it also includes a brief explanation of the reference collapsing rules knowing which is crucial to understand std::forward. – Alex Vask Jun 08 '22 at 12:11
  • I get that with the first overload it will forward an lvalue to an lvalue, but what I don't know is in what circumstance it forwards an lvalue to an rvalue in the first overload. because the comment in the first overload says it forwards an lvalue as either an lvalue or an rvalue. So it implies that the first overload can also forward an lvalue as an rvalue? – Anning Wuwang Jun 08 '22 at 12:54

1 Answers1

1

If _Ty is either a rvalue reference or a non-reference, then _Ty&& is (by reference collapsing rules) a rvalue reference. Hence the function call expression will be a xvalue (a kind of rvalue).

If _Ty is however an lvalue reference to type T, i.e. _Ty = T&, then the reference collapsing rules imply that also _Ty&& = T & && = T&. So then the function call will be an lvalue expression.

This is an implementation of std::forward. The template argument for _Ty is not intended to (and can't be) deduced. Instead it must be given explicitly.

Normally std::forward should only be used in the form std::forward<U>(u); where u is a forwarding reference parameter of the form U&& u in a function template with U a template parameter.

Under these conditions, if a rvalue was passed for u, U will be deduced to a non-reference and if an lvalue was passed U will be deduced to an lvalue reference.

Then std::forward<U>(u) will pass either a non-reference or an lvalue reference type as template argument for _Ty to std::forward accordingly and by the rule above, std::forward<U>(u) will have the same value category (rvalue or lvalue) as the argument to the forwarding reference u had. (However, it maps both prvalues and xvalues to xvalues.)


In the second overload the comment doesn't mention forwarding as lvalue, because the static_assert will trigger if the user tried to use it that way. It should not be allowed to call std::forward with a lvalue reference template argument while the argument is a rvalue. That would not match the intended usage I discussed above.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • I get that with the first overload it will forward an lvalue to an lvalue, but what I don't know is in what circumstance it forwards an lvalue to an rvalue in the first overload. because the comment in the first overload says it forwards an lvalue as either an lvalue or an rvalue. So it implies that the first overload can also forward an lvalue as an rvalue? – Anning Wuwang Jun 08 '22 at 12:54
  • @AnningWuwang See edit. – user17732522 Jun 08 '22 at 13:02
  • I write a similar function template T&& f(T& t) { return static_cast(t); } if you pass f(123); it won't compile. So it looks like you can't really pass a rvalue to the first overload? – Anning Wuwang Jun 08 '22 at 13:10
  • @AnningWuwang Well yes. That's what the comment says: It takes a lvalue. The second overload is for rvalue arguments. – user17732522 Jun 08 '22 at 13:11
  • yes, that's my exactly where my question is. Because without the second overload, you can't return a rvalue?(correctly me if i'm wrong please). If this is true, why the comment in the first overload says it forwards an lvalue to either an lvalue "or an rvalue"? instead of saying it forwards an lvalue to an lvalue only. so is it a mistake? – Anning Wuwang Jun 08 '22 at 13:16
  • @AnningWuwang Whether or not it produces a lvalue or a rvalue depends on what type the template argument is, not what value category the argument is. The template argument for `std::forward` is never deduced. It must always be given explicitly and generally doesn't match the value category of the argument. – user17732522 Jun 08 '22 at 13:18
  • so can you give me an example of how the first produce a rvalue please? – Anning Wuwang Jun 08 '22 at 15:22
  • @AnningWuwang `int x = 0; some_func(std::forward(x));`: `x` is a lvalue and `std::forward(x)` is xvalue (rvalue) – user17732522 Jun 08 '22 at 17:57
  • ah i see. in the comment rvalue refers to xvalue. so can you also give an example of it taking an lvalue and returning a lvalue please as the comment says it can also return an lvalue"// forward an lvalue as either an lvalue or an rvalue" – Anning Wuwang Jun 08 '22 at 19:11
  • @AnningWuwang `int x = 0; some_func(std::forward(x));` Now `std::forward(x)` is an lvalue. – user17732522 Jun 08 '22 at 20:24
  • Finally understood it. Thanks a lot for all the explanation! – Anning Wuwang Jun 15 '22 at 22:21