48
votes

I'm reading Overview of the New C++ (C++11/14) (PDF only), at Slide 288 it gives an implementation of std::forward:

template<typename T>                // For lvalues (T is T&),
T&& std::forward(T&& param)         // take/return lvalue refs.
{                                   // For rvalues (T is T),
    return static_cast<T&&>(param); // take/return rvalue refs.
}

And then gives another implemention in text:

The usual std::forward implementation is:

template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
    return static_cast<identity<T>::type&&>(param);
}

What is the difference? Why is latter the usual implementation?

4
In the first case, you could get away with std::forward(x), but in the second you must explicitly provide the template parameters, as they cannot be inferred. - Walter
@PiotrS. No, you don't. Why would that be necessary? Reference collapsing works outside of the immediate forwarding reference context. - Sebastian Redl
@SebastianRedl missed that it's identity, not remove_reference - Piotr Skotnicki
@PiotrS. You're right, lvalues need to be accepted. However, rvalues need not, so the solution is not a second overload, the solution is to always take an lvalue reference in the one that is there. - Sebastian Redl
@PiotrS. Turns out I'm wrong, the standard requires rvalues to be accepted. - Sebastian Redl

4 Answers

38
votes

The problem with the first is that you can write std::forward(x), which doesn't do what you want, since it always produces lvalue references.

The argument in the second case is a non-deduced context, preventing automatic deduction of the template argument. This forces you to write std::forward<T>(x), which is the right thing to do.

Also, the argument type for the second overload should be typename identity<T>::type& because the input to idiomatic use of std::forward is always an lvalue.

Edit: The standard actually mandates a signature equivalent to this one (which, incidentally, is exactly what libc++ has):

template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
20
votes

The implementation in libc++ uses std::remove_reference and two overloads. Here is the source (after removing some macros):

template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}

template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
    static_assert(!std::is_lvalue_reference<T>::value,
                  "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

but note that in C++14, std::forward is constexpr.

11
votes

The first case as Sebastian Redl said will always give you an lvalue reference. The reason is that an rvalue reference in the parameter would be passed as an lvalue reference, and the parameter T&& type is a universal reference rather than an rvalue reference.

Actually if the first case is correct, we don't even need forward any more. Here is an experiment to demonstrate how universal reference parameters are passed

template <typename T, typename U>
void g(T&& t, U&& u)
{
    std::cout << "t is lvalue ref: "
              << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
    std::cout << "t is rvalue ref: "
              << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
    std::cout << "u is lvalue ref: "
              << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 1
    std::cout << "u is rvalue ref: "
              << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 0
}

template <typename T, typename U>
void f(T&& t, U&& u)
{
    std::cout << "t is lvalue ref: "
              << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
    std::cout << "t is rvalue ref: "
              << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
    std::cout << "u is lvalue ref: "
              << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 0
    std::cout << "u is rvalue ref: "
              << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 1

    g(t, u);
}

int main()
{
    std::unique_ptr<int> t;
    f(t, std::unique_ptr<int>());
    return 0;
}

The program turns out that both t and u passed from f to g is lvalue references, despite that u is an rvalue reference in f. So in the first case the parameter of forward just doesn't have a chance to be an rvalue reference.

The identity is used to change the parameter type from universal reference to an rvalue reference (as mentioned by Redl, it's more precise to use std::remove_reference). However this change makes the template type deduction not possible any longer, so that the type parameter for forward is mandatory, as a result we shall write forward<T>(t).

But the second case in your question is not correct either, as also mentioned by Redl, the correct approach is an overload whose parameter is an lvalue reference.

The most straightforward implementation I can find is this

template <typename T>
T&& forward(typename identity<T>::type& param)
{
    return static_cast<T&&>(param);
}

It works for universal references, for example

template <typename T, typename U>
void f(T&& t, U&& u)
{
    ::forward<T>(t);
    ::forward<U>(u);
}

std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
// deduction in f:
//   T = unique_ptr&, decltype(t) = unique_ptr&
//   U = unique_ptr, decltype(u) = unique_ptr&& (but treated as an lvalue reference)
// specialization of forward:
//   forward<T> = forward<unique_ptr&>, param type = unique_ptr&
//                                      return type = unique_ptr&
//   forward<U> = forward<unique_ptr>,  param type = unique_ptr&
//                                      return type = unique_ptr&&
0
votes

Replies

What is the difference?

In practice there is no difference, the expected result in the execution is the same, given that only the way of writing is being more verbose

You are just typedefining the T and using the type defined through the typename, meaning literally no difference.

Why is latter the usual implementation?

I didn't follow this update so I can't say if they used this code officially in their repository, it's probably someone else's implementation and not the official one, whatever the reason is this:

They chose to do it this way, even though both implementations are invalid for the real scenario.

Note that both have the same result and do not diverge at all in the execution, that is, in the real scenario, diverging between them would not cause any difference or run/compilation error or anything like that

Code review

Missed add "typename" for compilation to succeed

template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
    return static_cast<**typename** identity<T>::type&&>(param);
}

Explanations

Why is invalid

#include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

int main() {
  int value = 5;
  forward<int>(value);
  return 1;
}

Real scenery

The error is because the template T referring to the void redir is of type int and when calling forward< int >(param) it is passing param which is an lvalue variable that was explained in the section Why is invalid

#include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  **redir(5);**
  return 0;
}

Solution

The code would be similar to this:

#include <iostream>

template< class T >
T&& forward( std::remove_reference_t<T> &&param)
{                                   
  return static_cast<T&&>(param);
}

template< class T >
T&& forward( std::remove_reference_t<T> &param)
{                                   
  return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  redir(5);
  return 0;
}

std::remove_reference_t is optional, even without using it the result will be the same.

They decided to use remove_reference_t due to good practices, that is, they are reinforcing that the first function expects int& and the second int&&.

Why std::remove_reference is irrelevant

Well, but the result explanation doesn't change is due to the following conversion rules:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

As we can, the only way to obtain an rvalue (T&&) is through the conversion T&& + T&&, or T&& only.

Why is it necessary to use 2 functions (std::forward) and not just static_cast in one function

<int&&> int&& && forward(int&& &&param) will result int&& forward(int&& param)
<int&> int& && forward(int& &&param) will result int& forward(int& param)
<int> int && forward(int &&param) will result int&& forward(int&& param)

Note that the second function: T&& forward( std::remove_reference_t &param) fills in just what was missing

<int> int && forward(int &param) will result int&& forward(int& param)

For this reason you need to declare 2 std::forward functions.