Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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:

  1. forward<Type&>(val) Here the type of T in forward will be T& and therefore the return type will be the identity transformation to T&
  2. forward<Type&&>(val) Here the type of T in forward will be T&& and therefore the return type will be the identity transformation to T&&

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
266 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...