How does std::forward receive the correct argument? - c++

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.

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

rvalue reference matching (perfect forwarding example)

I got confused by the following perfect forwarding function, where the template parameter T can match rvalue or lvalue references:
template<typename T>
void foo(T&& t){
T::A; // intended error to inspect type
}
int main(){
std::vector<int> a;
std::vector<int> && b = std::move(a);
foo(b); // T is std::vector<int> &
foo(std::move(a)); // T is std::vector<int>
}
I dont understand why the template argument deduction of T in foo is so different in these two cases? Whats the fundamental difference and important what is t's type in function foo.
std::move(a) returns a rvalue reference and b is already a rvalue reference (but has a name).
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Can anyone shine some light into this :-)
There is a special type deduction rule when && is used with templates.
template <class T>
void func(T&& t) {
}
"When && appears in a type-deducing context, T&& acquires a special
meaning. When func is instantiated, T depends on whether the argument
passed to func is an lvalue or an rvalue. If it's an lvalue of type U,
T is deduced to U&. If it's an rvalue, T is deduced to U:"
func(4); // 4 is an rvalue: T deduced to int
double d = 3.14;
func(d); // d is an lvalue; T deduced to double&
float f() {...}
func(f()); // f() is an rvalue; T deduced to float
int bar(int i) {
func(i); // i is an lvalue; T deduced to int&
}
Also, reference collapsing rule is a good read.
Check this out for a really good explanation:
perfect forwarding
If you think about the signature of your function, the type of the parameter is T&&. In your second example, T is deduced to vector<int>, that means that the type of the parameter to your function is vector<int>&&. So you are still passing by (rvalue) reference.
In the other case, you deduce T to vector<int>&. So the type of the argument is vector<int> & &&... or it would be, but references to references are not allowed. Reference collapsing takes over, and any double reference involving an lvalue reference become an lvalue reference. So you are passing by lvalue reference.
As far as b goes, this is a well known gotcha of rvalue references. Essentially, b's type is rvalue reference, but b itself still has a value category of lvalue. Think of it this way: b itself is a variable, that must live on the stack somewhere, and have an address. So it's an lvalue. This is precisely way calling std::forward when forwarding arguments is necessary. If you didn't do it, then they would always be forwarded as lvalue arguments.
I really recommend this Scott Meyers article: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers. Read it carefully!
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Yes, that's exactly it. It makes more sense if you think about rvalue reference function parameters: the caller is specifying that the function can do whatever it wants with the objects it gets. So from inside the function body, in order to make sure the code really can do whatever it wants with it, the parameter should be treated as an lvalue. That same argument can also be made for other rvalue references, including the b in your example, albeit to a lesser extent.
The expressions a and b are both lvalues, and the expression std::move(a) is an rvalue.
The deduction for the parameter T makes use of special reference collapsing rules so that the type of t is either an lvalue or an rvalue reference as needed to bind to the function call argument.

rvalue template argument implicitly used as lvalue, and std::forwarding working

This example on the usage of std::forward is puzzling me. This is my edited version:
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct A{
A(int&& n) { cout << "rvalue overload, n=" << n << "\n"; }
A(int& n) { cout << "lvalue overload, n=" << n << "\n"; }
};
template<typename> void template_type_dumper();
template<class T, class U>
unique_ptr<T> make_unique(U&& u){
//Have a "fingerprint" of what function is being called
static int dummyvar;
cout<<"address of make_unique::dummyvar: "<<&dummyvar<<endl;
//g++ dumps two warnings here, which reveal what exact type is passed as template parameter
template_type_dumper<decltype(u)>;
template_type_dumper<U>;
return unique_ptr<T>(new T(forward<U>(u)));
}
int main()
{
unique_ptr<A> p1 = make_unique<A>(2); // rvalue
int i = 1;
unique_ptr<A> p2 = make_unique<A>(i); // lvalue
}
The output is
address of make_unique::dummyvar: 0x6021a4
rvalue overload, n=2
address of make_unique::dummyvar: 0x6021a8
lvalue overload, n=1
and the warnings about reference to template_type_dumper show that in the first instantiation, decltype(u) = int&& and U = int, for the second decltype(u) = int& and U = int&.
It's evident that there are two different instantiations as expected, but her are my questions:
how can std::forward work here? In the first instantiation, its template argument is explicitly U = int, how can it know that it has to return a rvalue-reference? What would happen if I specified U&& instead?
make_unique is declared to take a rvalue-reference. How come u can be a lvalue-reference? Is there any special rule that I am missing?
make_unique is declared to take a rvalue-reference. How come u can be a lvalue-reference? Is there any special rule that I am missing?
make_unique is declared to take a reference. What kind that reference is is to be deduced. If an lvalue of type foo is passed, U is deduced as foo& and U&& becomes foo& because of the reference collapsing rules (basically, "combining" an lvalue reference with another reference always produces an lvalue reference; combining two rvalue references produces an rvalue reference). If an rvalue of type foo is passed, U is deduced as foo and U&& is foo&&.
This is one of the things that powers perfect forwarding: with U&& you can take both lvalues and rvalues, and U is deduced to match the appropriate value category. Then with std::forward you can forward the values preserving that same value category: in the first case, you get std::forward<foo&> which forwards an lvalue, and in the second one, you get std::forward<foo> which forwards an rvalue.
In the first instantiation, its template argument is explicitly U = int, how can it know that it has to return a rvalue-reference?
Because the return type of std::forward<T> is always T&&. If you pass int it returns int&&. If you pass int& it returns int& again because of the reference collapsing rules.
What would happen if I specified U&& instead?
You would have std::forward<int&&> and the reference collapsing rules make int&& && an rvalue reference still: int&&.