Given the following reference collapsing rules
T& &
-->T&
T&& &
-->T&
T& &&
-->T&
T&& &&
-->T&&
The third and fourth rule imply that T(ref qualifer) &&
is the identity transformation, i.e. T&
stays at T&
and T&&
stays at T&&
. Why do we have two overloads for std::forward
? Couldn't the following definition serve all purposes?
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
Here the only purpose the const std::remove_reference<T>&
serves is to not make copies. And the enable_if
helps ensure that the function is only called on non const values. I'm not entirely sure whether the const_cast
is needed since it's not the reference itself that's const.
Since forward
is always called with explicit template parameters there are two cases we need to consider:
forward<Type&>(val)
Here the type ofT
inforward
will beT&
and therefore the return type will be the identity transformation toT&
forward<Type&&>(val)
Here the type ofT
inforward
will beT&&
and therefore the return type will be the identity transformation toT&&
So then why do we need two overloads as described in http://en.cppreference.com/w/cpp/utility/forward?
Note: I am not sure if std::forward
is ever used with const
types, but I disabled forward
in that case, because I have never seen it used like that. Also move semantics don't really make sense in that case either.
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.
来源:https://stackoverflow.com/questions/38344332/why-does-stdforward-have-two-overloads