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

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.

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)

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.

Related

confused with the fowarding reference

In the c++ std type_traits file below the first overloaded function, the comment says:
forward an lvalue as either an lvalue or an rvalue
However the return value is just an rvalue reference, I wonder how it could be either an lavlue or an rvalue? Does it mean the returned value is a universal reference? If so what decides how to deduce it to an lvalue reference or an rvalue reference?
Also the second overload returns exactly the same thing, why does it say in the comment only forwarding as a rvalue reference without lvalue reference?
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
If _Ty is either a rvalue reference or a non-reference, then _Ty&& is (by reference collapsing rules) a rvalue reference. Hence the function call expression will be a xvalue (a kind of rvalue).
If _Ty is however an lvalue reference to type T, i.e. _Ty = T&, then the reference collapsing rules imply that also _Ty&& = T & && = T&. So then the function call will be an lvalue expression.
This is an implementation of std::forward. The template argument for _Ty is not intended to (and can't be) deduced. Instead it must be given explicitly.
Normally std::forward should only be used in the form std::forward<U>(u); where u is a forwarding reference parameter of the form U&& u in a function template with U a template parameter.
Under these conditions, if a rvalue was passed for u, U will be deduced to a non-reference and if an lvalue was passed U will be deduced to an lvalue reference.
Then std::forward<U>(u) will pass either a non-reference or an lvalue reference type as template argument for _Ty to std::forward accordingly and by the rule above, std::forward<U>(u) will have the same value category (rvalue or lvalue) as the argument to the forwarding reference u had. (However, it maps both prvalues and xvalues to xvalues.)
In the second overload the comment doesn't mention forwarding as lvalue, because the static_assert will trigger if the user tried to use it that way. It should not be allowed to call std::forward with a lvalue reference template argument while the argument is a rvalue. That would not match the intended usage I discussed above.

Understanding of the implementation of std::forward since C++11

(constexpr and noexcept are left out, since they seem irrelevant for the purpose of understanding how std::forward behaves.)
Based on my understanding of Scott Meyers' "Effective Modern C++",
a sample implementation of std::move in C++14 is the following
template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}
Given the explanation of what a forwarding (or "universal") reference is, this implementation, I think, is pretty clear to me:
the parameter param is of type T&&, i.e. an rvalue reference or lvalue reference (to whichever the type of the argument is), depending on whether the argument is an rvalue or lvalue in the caller; in other words param can bind both to rvalues and lvalues (i.e. anything); this is intended, since move should cast anything to rvalue.
decltype(auto) is just the concise way to express the return type based on the actual return statement.
the returned object is the same object param, casted to an rvalue reference (&&) to whatever the type T is, once its deduced referenceness is stripped off (the deduction is done on T&&, not on ⋯<T>&&).
In short, my understanding of the use of forwarding/universal references in the implementation of move is the following:
the forwarding/universal reference T&& is used for the parameter since it is intended to bind to anything;
the return type is an rvalue reference, since move is intended to turn anything to rvalue.
It'd be nice to know if my understanding is right so far.
On the other hand, a sample implementation of std::forward in C++14 is the following
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
My understanding is the following:
T&&, the return type, must be a forwarding/universal reference, since we want forward to return either by rvalue reference or by lvalue reference, hence type deduction takes place on the return type here (unlike what happens for move, where type deduction takes place on the parameter side) is an rvalue reference to whichever template type argument is passed to forward;
since T encodes the lvalue/rvalue-ness of the actual argument which binds the callers' parameter that is passed as argument to forward, T itself can result to be actual_type& or actual_type, hence T&& can be either an lvalue reference or rvalue reference.
The type of param is an lvalue reference to whatever the type T is, once its deduced referenceness is stripped off. Actually in std::forward type deduction is disabled on purpose, requiring that the template type argument be passed explicitly.
My doubts are the following.
The two instances of forward (two for each type on which is it called, actually) only differ for the return type (rvalue reference when an rvalue is passed, lvalue reference when an lvalue is passed), since in both cases param is of type lvalue reference to non-const reference-less T. Isn't the return type something which does not count in overload resolution? (Maybe I've used "overload" improperly, here.)
Since the type of param is non-const lvalue reference to reference-less T, and since an lvalue reference must be to-const in order to bind to an rvalue, how can param bind to an rvalue?
As a side question:
can decltype(auto) be used for the return type, as it is done for move?
forward is essentially a machinery to conserve the value category in perfect forwarding.
Consider a simple function that attempts to call the f function transparently, respecting value category.
template <class T>
decltype(auto) g(T&& arg)
{
return f(arg);
}
Here, the problem is that the expression arg is always an lvalue regardless of whether arg is of rvalue reference type. This is where forward comes in handy:
template <class T>
decltype(auto) g(T&& arg)
{
return f(forward<T>(arg));
}
Consider a reference implementation of std::forward:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
constexpr T&& forward(remove_reference_t<T>&& t) noexcept
{
static_assert(!std::is_lvalue_reference_v<T>);
return static_cast<T&&>(t);
}
(You can use decltype(auto) here, because the deduced type will always be T&&.)
In all the following cases, the first overload is called because the expression arg denotes a variable and hence is an lvalue:
If g is called with a non-const lvalue, then T is deduced as a non-const lvalue reference type. T&& is the same as T, and forward<T>(arg) is a non-const lvalue expression. Therefore, f is called with a non-const lvalue expression.
If g is called with a const lvalue, then T is deduced as a const lvalue reference type. T&& is the same as T, and forward<T>(arg) is a const lvalue expression. Therefore, f is called with a const lvalue expression.
If g is called with an rvalue, then T is deduced as a non-reference type. T&& is an rvalue reference type, and forward<T>(arg) is an rvalue expression. Therefore, f is called with an rvalue expression.
In all cases, the value category is respected.
The second overload is not used in normal perfect forwarding. See What is the purpose of std::forward()'s rvalue reference overload? for its usage.

Using std::forward with a non-forwarding, plain old reference

Asking for a friend:
Why does std::forward in the following code cast the parameter c to an rvalue ?
template <typename T>
void f (T& c) {
using value_type = typename std::remove_reference_t<T>;
std::vector<value_type> v;
// debugger reveals: push_back( T&& value ) is called here
v.push_back(std::forward<T>(c));
}
Note that c is not a universal/forwarding reference here. I am aware of the fact that this function would most probably be of more use if it actually was, but curious all the same.
To understand this situation, you have to understand why forwarding references work. Given a definition like
template <typename T>
void foo(T&& t) {}
when you write something like
some_type some_object;
foo(some_object);
template deduction deduces T to be some_type&. Now the parameter t has the type some_type& &&. Since you can't have references to references, reference collapsing rules are applied and some_type& && is collapsed to some_type&.
If, instead, you write something like
some_type some_object;
foo(std::move(some_object));
template deduction deduces T to be some_type. Now the parameter t has the type some_type&&. That's a perfectly valid type, so no reference collapsing is done.
Now we get to std::forward. All std::forward<U> does is cast its parameter to U&&. If U is some_type, as in the second case above, the parameter is cast to some_type&&. It remains an rvalue-reference. If U is some_type&, as in the first case above, reference collapsing is performed again, and some_type& && becomes some_type&. So std::forward returns an lvalue-reference.
So the ultimate answer to your original question is that the return type of std::forward only depends on the type passed as std::forward's template parameter. Since T in your case will always be deduced as a non-reference type, std::forward will always return an rvalue-reference.
std::forward<T>(c) is equivalent to static_cast<T&&>(c).
If T&& is a forwarding reference then this allows an lvalue to be forwarded as an lvalue because T will be deduced as an lvalue reference type and T&& will be the same lvalue reference type by the reference-collapsing rules. In your situation, T&& is not a forwarding reference, so this doesn't work.
Well, the definition of std::forward<T>(x) is to cast x to type T&&. If you pass a non-reference as argument you’ll get an rvalue reference T&& back. Since your T cannot be a reference type (you cannot have a reference to a reference), it must be a non-reference type.

How does std::forward receive the correct argument?

Consider:
void g(int&);
void g(int&&);
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
int main()
{
f(10);
}
Since the id-expression x is an lvalue, and std::forward has overloads for lvalues and rvalues, why doesn't the call bind to the overload of std::forward that takes an lvalue?
template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
It does bind to the overload of std::forward taking an lvalue:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
It binds with T == int. This function is specified to return:
static_cast<T&&>(t)
Because the T in f deduced to int. So this overload casts the lvalue int to xvalue with:
static_cast<int&&>(t)
Thus calling the g(int&&) overload.
In summary, the lvalue overload of std::forward may cast its argument to either lvalue or rvalue, depending upon the type of T that it is called with.
The rvalue overload of std::forward can only cast to rvalue. If you try to call that overload and cast to lvalue, the program is ill-formed (a compile-time error is required).
So overload 1:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
catches lvalues.
Overload 2:
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
catches rvalues (which is xvalues and prvalues).
Overload 1 can cast its lvalue argument to lvalue or xvalue (the latter which will be interpreted as an rvalue for overload resolution purposes).
Overload 2 can can cast its rvalue argument only to an xvalue (which will be interpreted as an rvalue for overload resolution purposes).
Overload 2 is for the case labeled "B. Should forward an rvalue as an rvalue" in N2951. In a nutshell this case enables:
std::forward<T>(u.get());
where you are unsure if u.get() returns an lvalue or rvalue, but either way if T is not an lvalue reference type, you want to move the returned value. But you don't use std::move because if T is an lvalue reference type, you don't want to move from the return.
I know this sounds a bit contrived. However N2951 went to significant trouble to set up motivating use cases for how std::forward should behave with all combinations of the explicitly supplied template parameter, and the implicitly supplied expression category of the ordinary parameter.
It isn't an easy read, but the rationale for each combination of template and ordinary parameters to std::forward is in N2951. At the time this was controversial on the committee, and not an easy sell.
The final form of std::forward is not exactly what N2951 proposed. However it does pass all six tests presented in N2951.
why doesn't the call bind to the overload of std::forward that takes an lvalue?
It does exactly that, but std::forward does not deduce its template argument, you tell it what type it is, and that's where the magic occurs. You're passing a prvalue to f() so f() deduces [T = int]. It then calls the lvalue overload of forward, and due to reference collapsing both the return type and static_cast<T&&> that happens within forward will be of type int&&, thus calling the void g(int&&) overload.
If you were to pass an lvalue to f()
int x = 0;
f(x);
f() deduces [T = int&], again the same lvalue overload of forward gets called, but this time the return type and static_cast<T&&> are both int&, again because of the reference collapsing rules. This will then call the void g(int&) overload instead.
Live demo
Howard already has a good answer for your question about why the rvalue overload of forward is required, but I'll add a contrived example that shows the two versions in action.
The basic idea behind the two overloads is that the rvalue overload will be invoked in cases where the result of the expression you pass to forward yields an rvalue (prvalue or xvalue).
Say you have a type foo that has a ref-qualified pair of get() member function overloads. The one with the && qualifier returns an int while the other returns int&.
struct foo
{
int i = 42;
int get() && { return i; }
int& get() & { return i; }
};
And say f() invokes the get() member function on whatever was passed to it, and forwards that on to g()
template<class T>
auto f(T&& t)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}
foo foo1;
f(foo1); // calls lvalue overload of forward for both calls to forward
f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
// but calls rvalue overload for outer call to forward
Live demo
How does std::forward receive the correct argument?
For perfect forwarding,as your code:
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
Note this:std::forward requires both a function argument and a template type argument.
The template parameter T will encode whether the argument passed to param was an lvalue or an rvalue and then forward use it.
In this case,not matter x refer what ,x itself is an lvalue,choice overload 1 ,and x is forwarding(universal) reference parameters.Rules as follow:
if f the argument's expr is lvalue ,Both x and T will lvalue reference
type
if f the argument's expr is rvalue ,T will be non-reference type.
For example use gcc source code :
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); } //overload 1
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);
}// overload 2
if pass function f lvalue of type is string or string&,for
forward function _Tp will be string&,__t will be string & &
is equal to string&,_Tp&& will be string& && is equal to
string&.(reference collapsing)
if pass function f rvalue of type is string or string&&,for
forward function _Tp will be string,__t will be
string&,_Tp&& will be string&&.
What the function do?
if rvalue to rvalue(Overload 2) or lvalue to lvalue(Overload 1) ,nothing to do,just return.
If you're not careful or something else, lead rvalue to lvalue, gives you a compilation error by static_assert.(Overload 2)
If you do lvalue to rvalue(Overload 1),it is same with std::move,but not convenience.
So just as Scott Meyers say:
Given that both std::move and std::forward boil down to casts, the
only difference being that std::move always casts, while
std::forward only sometimes does, you might ask whether we can
dispense with std::move and just use std::forward everywhere. From a
purely technical perspective, the answer is yes: std::forward can do
it all. std::move isn’t necessary. Of course, neither function is
really necessary, because we could write casts everywhere, but I
hope we agree that that would be, well, yucky.
What is the rvalue overload for?
I'm not sure, it should be used where you really need it.Such as Howard Hinnant says:
Overload 2 is for the case labeled "B. Should forward an rvalue as an
rvalue" in N2951. In a nutshell this case enables:
std::forward<T>(u.get());
where you are unsure if u.get() returns an lvalue or rvalue, but
either way if T is not an lvalue reference type, you want to move the
returned value. But you don't use std::move because if T is an lvalue
reference type, you don't want to move from the return.
In a word,it allow us use T's type to decide whether move or not.

How are std::move template parameters deduced?

Suppose we have:
foo(A&& a);
if you do
A a;
foo(a);
it won't compile and complain cannot bind a lvalue to A&&. that's perfectly fine.
However, given the signature of std::move,
template<class T> typename remove_reference<T>::type&& std::move(T&& a);
Looks like it takes a rvalue reference, just as in foo, why the following code complies?
A a;
std::move(a);
isn't a is a lvalue?
furthur, it is said the compile will instantiate:
typename remove_reference<A&>::type&& std::move(A& && a);
I don't understand why it is not:
typename remove_reference<A>::type&& std::move(A && a);
it looks to me a is of type A, not A&.
Nope move doesn't take an rvalue-reference, it takes what has been dubbed a universal reference by the community. Template parameters being type-deduced behave according to the rules of reference collapsing. This means:
if T is K, then T&& will simply be K&&;
if T is K&, then T&& will collapse to K&;
if T is K&&, then T&& will collapse to T&&.
It's like a logical-AND of the & and the && where & is 0 and && is 1:
& &&
|-----------|
& | & | & |
|-----|-----|
&& | & | && |
|-----------|
And that's how move works for both rvalues and lvalues.
Examples:
template<typename T>
void f(T&&);
f<int> // T is int; plugging int into T makes int&& which is just int&&
f<int&> // T is int&; plugging int& into T is int& && which collapse to int&
f<int&&> // T is int&&; plugging int&& into T is int&& && which collapse to int&&
Note that reference collapsing only happens with template parameters; you can't directly type int&& && and expect it to compile. Of course, you don't specify types manually like that. Those are just to show what references collapse to.
So you'd really call it like this:
int i;
f(i); // T is int&; int& && collapses to int&
f(4); // T is int&&; int&& && collapses to int&&
Reference collapsing is also the reason why move doesn't return a T&&: the references would collapse if T were an lvalue reference and make move just return an lvalue reference. You do remove_reference to get to a non-reference type so that the && will really mean "rvalue-reference".
You can learn more here: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
The syntactic form T&& in the context of type deduction (which includes template argument deduction, but for instance also the deduction of the type of a variable declared as auto) does not indicate an rvalue reference, but rather what Scott Meyers calls a [universal reference]. Please notice, that only the very particular syntactic form T&& denotes a universal reference, while other, similar forms are not regarded as such. For instance:
template<typename T>
void foo(T&& t); <-- T&& is a universal reference
template<typename T>
void foo(T const&& t); <-- T const&& is NOT a universal reference
template<typename T>
void foo(S<T>&& t); <-- S<T>&& is NOT a universal reference
template<typename T>
struct S { void foo(T&& t); }; <-- T&& is NOT a universal reference
Universal references can bind both to lvalues and to rvalues. If an lvalue of type A is bound, then T is deduced to be A& and the type of the argument resolves into A& (lvalue reference) due to the rule of reference collapsing (A& && becomes A&). If an rvalue of type A is bound, then T is deduced to be A and the type of the argument resolves into A&& (rvalue reference).
[Note: Reference collapsing rule might seem complicated, but they are actually quite easy: to quote Stephan T. Lavavej, "lvalue references are contagious", meaning that when the forms T&& &, T& &, or T& && get instantiated, they always resolve into T& - only the form T&& && is resolved into T&&]
This is why the std::move function template will be instantiated as follows when the argument is an lvalue (T is deduced to be T&):
typename remove_reference<A&>::type&& std::move(A& && a);
while it will be instantiated as follows when the argument is an rvalue (T is deduced to be A)
typename remove_reference<A>::type&& std::move(A&& a);
Despite what others have said, the standard only talks about rvalue references.
The key to how this works for std::move is an explicit special rule in the rules for template argument deduction:
[...] If [the declared function parameter type] is an rvalue reference
to a cv-unqualified template parameter and the argument is an lvalue,
the type “lvalue reference to A” is used in place of A for type
deduction.[...]
The other part are the rules for reference collapsing, which say that
If [...] a type template-parameter [...] denotes a type TR that is a reference to
a type T, an attempt to create the type “lvalue reference to cv TR”
creates the type “lvalue reference to T”, while an attempt to create
the type “rvalue reference to cv TR” creates the type TR.
Now in template<class T> typename remove_reference<T>::type&& std::move(T&& a); the function parameter a matches above rule ("rvalue reference to cv-unqualified template parameter"), so the deduced type will be an lvalue reference to the argument type, if the argument is an lvalue. In your case that leads to T = A&.
Substituting that into the declaration of move yields
remove_reference<A&>::type&& std::move<A&>(A& && a);
Using the definition of remove_reference and the reference collapsing rule (rvalue reference to TR => TR), makes this:
A&& std::move<A&>(A& a);
Scott Meyer's universal reference concept, as put forward in other answers, is a helpful way to remember this surprising effect of the combination of the rules for type deduction and of reference collapsing: rvalue references to a deduced type may end up being lvalue references (if the type may be deduced to be a lvalue reference). But there are no universal references int the standard. As Scott Meyers says: it is a lie - but a lie that is more helpful than the truth...
Note that std::forward is a different twist on this theme: it uses an extra indirection to prevent argument deduction (so that the type must be given explicitly), but also uses reference collapsing to forward lvalues as lvalues and rvalues as rvalues.