5

I know that the second overload of std::forward:

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

is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)

There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):

  1. Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.

For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:

// transforming wrapper 
template<class T>
void wrapper(T&& arg)
{
    foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}

where the type of arg may be

struct Arg
{
    int i = 1;
    int  get() && { return i; } // call to this overload is rvalue
    int& get() &  { return i; } // call to this overload is lvalue
};

I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?

Cppreference states:

This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.

As an example:

void func(int& lvalue)
{
    std::cout << "I got an lvalue!" << std::endl;
}

void func(int&& rvalue)
{
    std::cout << "I got an rvalue!" << std::endl;
}

template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
    return static_cast<T&&>(t);
}

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

template <typename T>
void wrapper(T&& t)
{

    func(myForward<T>(t).get());
}

int main()
{
    foo f;
    wrapper(f);
    wrapper(foo());

    return 0;
}

This prints:

I got an lvalue!
I got an rvalue!

just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:

func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));

Still, I can't wrap my head around why anyone would need to add the outer forward.

Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?

Ruperrrt
  • 489
  • 2
  • 13
  • You are quoting some unknown source. Searching the link above the quote containing "example, if a wrapper does not" for that string does not show that string? Is the "quoted" section above supposed to be a quote, or is it just misformatted? – Yakk - Adam Nevraumont Aug 04 '21 at 13:42
  • Are you paraphrasing while pretending to quote? Am I confused? Did you drop the wrong link? – Yakk - Adam Nevraumont Aug 04 '21 at 13:44
  • Ah, you are quoting https://en.cppreference.com/w/cpp/utility/forward – Yakk - Adam Nevraumont Aug 04 '21 at 13:45
  • It is probably just an example of what can happen in complicated template programming scenarios. To simplify even more: we probably want the code like `myForward(42);` to compile, and it will not compile without `std::remove_reference_t&& t` overload. – dewaffled Aug 04 '21 at 14:10
  • 2
    Duplicate: https://stackoverflow.com/q/41543571/5376789 – xskxzr Aug 05 '21 at 06:14

1 Answers1

2

Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?

It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thank you for your answer! It made me have a closer look at the value categories involved in the full expression. Consequently, I wonder if the second overload of std::forward might cause dangling references when used with prvalues (see my edit). – Ruperrrt Aug 05 '21 at 10:47
  • @Ruperrrt - please read https://meta.stackexchange.com/q/93513/317773 – StoryTeller - Unslander Monica Aug 05 '21 at 10:54
  • Thank you for highlighting that! Moved the edit to a follow-up question. – Ruperrrt Aug 05 '21 at 13:05