Why does std::forward have two overloads?

A good place to start would be Howard Hinnant’s answer and paper on std::forward().


Your implementation handles all the normal use-cases correctly (T& --> T&, T const& --> T const&, and T&& --> T&&). What it fails to handle are common and easy-to-make errors, errors which would be very difficult to debug in your implementation but fail to compile with std::forward().

Given these definitions:

struct Object { };

template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& my_forward(const typename std::remove_reference<T>::type& val) {
    return static_cast<T&&>(const_cast<T&&>(val));
}

template <class T>
void foo(T&& ) { }

I can pass non-const references to const objects, both of the lvalue variety:

const Object o{};
foo(my_forward<Object&>(o));    // ok?? calls foo<Object&>
foo(std::forward<Object&>(o));  // error

and the rvalue variety:

const Object o{};
foo(my_forward<Object>(o));    // ok?? calls foo<Object>
foo(std::forward<Object>(o));  // error

I can pass lvalue references to rvalues:

foo(my_forward<Object&>(Object{}));   // ok?? calls foo<Object&>
foo(std::forward<Object&>(Object{})); // error

The first two cases lead to potentially modifying objects that were intended to be const (which could be UB if they were constructed const), the last case is passing a dangling lvalue reference.

Leave a Comment