4

I suppose I am confused with std::forward. My function which uses std::forward is following, but it is much simplified and modified to make explanation easily.

// This is an example code to explain my question simply.
template <typename Element>
void add(Element&& element) {
    static std::vector vec;
    vec.push_back(std::forward<Element>(element));
}

I tried two case with the function above; Case 1 lvalue argument and Case 2 rvalue argument.

Case 1: lvalue argument

auto some_class = SomeClass();
add(some_class);

Case 2: rvalue argument

add(SomeClass());

In debugger both cases passes the same following parts, std::forward part and std::vector part.

std::forward part:

template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

std::vector part:

#if __cplusplus >= 201103L
  void
  push_back(value_type&& __x)
  { emplace_back(std::move(__x)); }

It seems std::forward part converts both cases to rvalue reference, &&, because it uses static_cast<_Tp&&>. And std::vector is treated both elements as rvalue reference because it uses std::move().

I have expected augment of Case 1 is lvalue because it has its own name and Case 2 is rvalue because it does not have its own name. I also have expected std::forward converts Case 1 to lvalue reference and Case 2 to rvalue reference. Are my understandings of lvalue, rvalue and std::forward correct? If so, why std::forward converts both as rvalue reference, &&.

If I made a mistake, I am sorry for taking your time.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
mora
  • 2,217
  • 4
  • 22
  • 32

2 Answers2

3

why std::forward converts both as rvalue reference

It shouldn't. According to the rule of forwarding reference, when an lvalue is passed to add, the template type argument Element will be deduced as SomeClass&. Then std::forward<SomeClass&>(element) will be invoked, and the instantiation of std::forward would be

// before reference collapsing
constexpr SomeClass& &&
forward(SomeClass& __t) noexcept
{ return static_cast<SomeClass& &&>(__t); }

and

// after reference collapsing
constexpr SomeClass&
forward(SomeClass& __t) noexcept
{ return static_cast<SomeClass&>(__t); }

So for the 1st case, std::forward will return an lvalue. An lvalue-reference returned from function is an lvalue.

BTW, for the 2nd case, the templare argument Element will be deduced as SomeClass, then you can do the same inference as above, at last the instantiation of std::forward would be

constexpr SomeClass&&
forward(SomeClass& __t) noexcept
{ return static_cast<SomeClass&&>(__t); }

An rvalue-reference returned from funtion is an rvalue.


The result you got seems weird, for the 1st case, std::vector::push_back(const T&) should be invoked. (I tried a mcve, here)

Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Thank you for answering in detail. I agree my question above is weird. I read my code again. Thank you very much again. – mora May 14 '17 at 09:22
1

The part that you're missing is reference collapsing. When passing in an lvalue, it will have type T& (or const T&) for some T. If you add this into the forward template, you get:

return static_cast<T& &&>(__t);

Due to reference collapsing rules, this collapses down to T&.

Effective Modern C++ covers this in Item 28. Basically:

  • Lvalues of type T are deduced as T&.
  • Rvalues of type T are deduced as T.

With this, and the reference collapsing rules above, hopefully you can understand how std::forward works.

Yuushi
  • 25,132
  • 7
  • 63
  • 81