0

It is said that std::forward() perfectly forwards a lvalue to lvalue, and rvalue to rvalue. But the code below seems to show a different conclusion?

when I run the code below

#include <iostream> class A { public: int x; A() {x = 0;} explicit A(int x) : x(x) {} A& operator= (A&& other) { std::cout << " && =" << std::endl; x = other.x; } A& operator= (const A& other) { std::cout << " & =" << std::endl; x = other.x; } }; int main() { A p1(1), p2, p3; p2 = p1; p3 = std::forward<A>(p1); return 0; } 

The result is:

 & = && = 

it seems that std::forward<A>(p1) forwards p1 as rvalue.

The source code of std::forward() is:

 /** * @brief Forward an lvalue. * @return The parameter cast to the specified type. * * This function is used to implement "perfect forwarding". */ template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } /** * @brief Forward an rvalue. * @return The parameter cast to the specified type. * * This function is used to implement "perfect forwarding". */ template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); } 

gdb shows that when running p3 = std::forward<A>(p1);, it steps into the first function forward(typename std::remove_reference<_Tp>::type& __t) noexcept and returns a value of type A &&

(gdb) p __t $2 = (std::remove_reference<A>::type &) @0x7fffffffd1cc: { x = 1 } (gdb) p static_cast<_Tp&&>(__t) $3 = (A &&) @0x7fffffffd1cc: { x = 1 } 

I am totally confused. Would you please explain what happened when calling std::forward()? If std::forward<A>(p1) forwards p1 as rvalue, then what's the difference of std::forward and std::move?

2
  • 2
    forward is just a conditional move. If the template argument is T & it does nothing, and otherwise it acts as a std::move. It's intended to be used with forwarding references. When outside of a template, there's no point in using it, since you can unconditionally std::move or not move. Commented Sep 21, 2023 at 8:21
  • std::forward is meant to be used with reference collapsing, which is how universal references work. It doesn't make sense to pass it a non-reference type, it isn't an expected use case for that function. Try std::forward<A&>, which will give you the desired behaviour, which is also equivalent to not using std::forward at all. It is basically meant to deduced whether or not to std::move should be used based on what kind of reference a template argument is. Commented Sep 21, 2023 at 13:38

1 Answer 1

2

If std::forward<A>(p1) forwards p1 as rvalue, then what's the difference of std::forward and std::move?

The difference is that std::forward<A&>(p1) returns an lvalue, and std::move does not.

You're misusing std::forward. Passing a constant template type argument is pointless. They're useful when a function template has a forwarding reference argument whose type is deduced from function argument: Example:

template<class T> void foo(T&& ref) { T t = std::forward<T>(ref); // copy or move ... foo(p1); // T is deduced as A& 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.