Search code examples
c++c++11lvaluervalue

Why does std::forward converts lvalue and rvalue to rvalue reference?


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.


Solution

  • 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)