Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why there are two signatures of std::forward?

As stated at cplusplus.com, std::forward has two signatures:

template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& arg) noexcept;

The typical use of std::forward is to preserve rvalueness while passing arguments to other functions. Let's illustrate this with an example:

void overloaded(int &)  { std::cout << "lvalue"; }
void overloaded(int &&) { std::cout << "rvalue"; }

template <typename T>
void fwd(T && t)
{
  overloaded(std::forward<T>(t));
}

When we call fwd(0), T deduces to int (t has type int &&). Then we call std::forward<int>(t). The result of that call is expression of type int && so the second version of overloaded function is selected and the program prints "rvalue" to the standard output.

When we call fwd(i) (where i is some int variable), T deduces to int& (t has type int &). Then we call std::forward<int&>(t). The result of that call (after applying reference collapsing rules) is expression of type int & so the first version of overloaded function is selected and the program prints "lvalue" to the standard output.

In both these cases we use the first overload of std::forward (the one taking typename remove_reference<T>::type& arg). It is because even if the type of t is int && it binds to lvalue reference (because named variable of type "rvalue reference to something" is itself lvalue and lvalues can't bind to rvalue reference).

Question 1:

What is that second overload of std::forward for? Can you think of some practical example, that uses the overload taking arg by rvalue reference?

Question 2:

cplusplus.com says:

Both signatures return the same as:

static_cast<decltype(arg)&&>(arg)

The problem I have with that, is that I'm pretty sure that it is wrong. When we try to return this from the first overload of std::forward we get a compilation error.

When fwd is called with int rvalue, it calls the first overload of std::forward with T = int. Then, decltype(arg) will become int& so static_cast<decltype(arg)&&>(arg) will collapse to static_cast<int&>(arg). But the return type is int && and we get compilation error:

cannot bind ‘std::remove_reference<int>::type {aka int}’ lvalue to ‘int&&’

Both overloaded versions of std::forward should return static_cast<T&&>(arg). Am I right?

Do you think that the quote from cplusplus.com is a mistake?

like image 378
Peter C Avatar asked Nov 09 '22 06:11

Peter C


1 Answers

class Foo {};

auto f = [](){ return Foo{}; };
auto g = []()->Foo&{ static Foo x; return x; };

template<class T>
std::false_type is_rvalue( T& ) { return {}; }
template<class T>
std::true_type is_rvalue( T&& ) { return {}; }

template<class T, class F>
auto test( F&& f ) {
  return is_rvalue( std::forward<T>( f() ) );
}

int main() {
  std::cout << test<Foo>(f) << "," << test<Foo&>(g) << "\n";
}

It is a bit contrived; but imagine where you have some factory that might produce a Foo or a Foo& depending on some unimportant details, but you know you want to forward it based on a second type T to T&&. You know that if T is not a reference, then it won't produce Foo, but if T is a reference it might.

like image 154
Yakk - Adam Nevraumont Avatar answered Nov 15 '22 12:11

Yakk - Adam Nevraumont