confused with the fowarding reference - c++

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.

Related

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.

Why does std::move take rvalue reference as argument?

According to cppreference.com, move has signature
template< class T >
typename std::remove_reference<T>::type&& move( T&& t ) noexcept;
Why does it take a rvalue reference T&& t as its arugment?
Also when I tried the following code
void foo(int&& bar) {
cout << "baz" << endl;
}
int main(){
int a;
foo(a);
}
I got an error from the compiler "an rvalue reference cannot be bound to an lvalue"
What is going on? I'm so confused.
It's not an rvalue reference, but a forwarding reference; which could preserve the value category of the argument. That means std::move could take both lvalue and rvalue, and convert them to rvalue unconditionally.
Forwarding references are a special kind of references that preserve the value category of a function argument, making it possible to forward it by means of std::forward. Forwarding references are either:
1) function parameter of a function template declared as rvalue
reference to cv-unqualified type template parameter of that same
function template:
2) auto&& except when deduced from a brace-enclosed initializer list.
On the other hand, int&& is an rvalue reference; note the difference here, if a function template parameter has type T&& with template parameter T, i.e. a deduced type T, the parameter is a forwarding reference.

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.
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.

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.