1
votes

Referring to std::forward

For all the overloads, the return type has been specified as T&& (ignoring constexpr).

But in the description attached to the following example:

template<class T>
void wrapper(T&& arg) 
{
    // arg is always lvalue
    foo(std::forward<T>(arg)); // Forward as lvalue or as rvalue, depending on T
}
  • If a call to wrapper() passes an rvalue std::string, then T is deduced to std::string (not std::string&, const std::string&, or std::string&&), and std::forward ensures that an rvalue reference is passed to foo.

  • If a call to wrapper() passes a const lvalue std::string, then T is deduced to const std::string&, and std::forward ensures that a const lvalue reference is passed to foo.

  • If a call to wrapper() passes a non-const lvalue std::string, then T is deduced to std::string&, and std::forward ensures that a non-const lvalue reference is passed to foo.

In the two instances above after the first, an lvalue reference and not an rvalue reference (as implied by T&&, is this understanding correct?) has been documented as being passed to foo.

If the above understanding is correct, how come the return value has been specified as T&&?

1
T&& does not always mean a rvalue reference. I suggest to read Scott Meyers article "Universal References in C++11". - zett42
Take a look at how reference collapsing work. - Snps
What else do you think the return value should be specified as? - M.M

1 Answers

0
votes

There is a distinction between

void f1(int&& a) {}

template<class T>
void f2(T&& a) {}

The first version is defining f1 to work on rvalues. The second version, on the other hand, is a template function accepting a universal (or in some references, forwarding) reference as its argument.

To understand the mechanics of std::forward, you should call f2 with different arguments, as in:

#include <iostream>

template <class T> void f2(T &&a) { std::cout << __PRETTY_FUNCTION__ << '\n'; }

int main() {
  int a{5};

  f2(5);
  f2(a);

  return 0;
}

When you compile the code, say with g++, you get the following output from your program:

./a.out
void f2(T &&) [T = int]
void f2(T &&) [T = int &]

As you can see, in the first call T is deduced to be int, whereas in the second call it is deduced to be int &. Due to the reference collapsing rules, as already mentioned in the comments to your question, T && will give you T whereas T& && will give you T&. In short, you observing T&& as the return type does not mean that the functions return an rvalue reference. In fact, in the template functions, && is like an identity operator. When combined with T&, it will give you T&; otherwise, it will give you T.

There is a really nice talk at CppCon2016 by Arthur O'Dwyer on this topic. Maybe you can have a look at it to understand the template type deduction rules, which will help you in clarifying the std::forward behaviour.