I am refreshing my memory on how perfect forwarding works in C++. I realize that a call to std::forward is forced to provide an explicit template parameter for a reason (i. e. when dealing with rvalue references that are actually lvalues), however when doing a sanity check on actual code, I was surprised by this (somewhat related) scenario:
#include <iostream>
#include <utility>
#include <type_traits>
template<class T>
T&& fwd(T& t) {
return static_cast<T&&>(t);
}
template<class T>
T&& fwd(T&& t) {
return static_cast<T&&>(t);
}
int main()
{
int lnum = 3;
if (std::is_rvalue_reference<decltype(fwd(lnum))>::value)
std::cout << "It's rref." << std::endl; // this get's printed on screen
else
std::cout << "It's lref." << std::endl;
return 0;
}
If I understand reference collapsing correctly (and I believe I do), type deduction should go like this:
int& && fwd(int& & t) {
return static_cast<int& &&>(t);
}
leading to
int& fwd(int& t) {
return static_cast<int&>(t);
}
Clearly that's not the case. What am I missing here?
Actually, no referencing collapsing occurs. The relevant function template to pay attention to, i.e., the one selected, is:
template<class T>
T&& fwd(T& t) { // <-- not a forwarding reference
return static_cast<T&&>(t);
}
Note that this function template has no forwarding references – the function parameter, t, is just an lvalue reference (T& t).
The T template parameter is deduced to int – not int& – because t is not a forwarding reference but just an lvalue reference. If you simply replace T by int in the function template above, then you will obtain:
template<class T>
int&& fwd(int& t) {
return static_cast<int&&>(t);
}
No reference collapsing is applied as there is no such a thing here that would otherwise end up becoming a reference to a reference (e.g., int& && or int&& &&).
Firstly, the function that is called is T&& fwd(T& t). As such, there is no forwarding reference parameter. The parameter is an lvalue reference and the deduced T is int. As such, there are no references to collapse and the static cast produces int&&.
If the called function had been T&& fwd(T&& t) (i.e. if the better matching overload didn't exist), then your explanation of reference collapsing would be correct (except for the parameter which would be int& && which also collapses int&) and the return type would indeed be lvalue reference.
Related
I'm trying to understand perfect forwarding a bit deeply and faced a question I can't figure out myself.
Suppose this code:
void fun(int& i) {
std::cout << "int&" << std::endl;
}
void fun(int&& i) {
std::cout << "int&&" << std::endl;
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
int main()
{
wrapper(4);
}
It prints int&. To fix this one should use std::forward. That's clear. What is unclear is why it is so.
What the code above unwraps into is:
void fun(int & i)
{
std::operator<<(std::cout, "int&").operator<<(std::endl);
}
void fun(int && i)
{
std::operator<<(std::cout, "int&&").operator<<(std::endl);
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void wrapper<int>(int && i)
{
fun(i);
}
#endif
int main()
{
wrapper(4);
return 0;
}
So i should have rvalue type of int&&. The question is: why do I need std::forward here since compiler already knows that i is int&& not int& but still calls fun(it&)?
Types and value categories are different things.
Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.
i, the name of the variable, is an lvalue expression, even the variable's type is rvalue-reference.
The following expressions are lvalue expressions:
the name of a variable, ... Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
...
That's why we should use std::forward to preserve the original value category of a forwarding reference argument.
why do I need std::forward here since compiler already knows that i is
int&& not int& but still calls fun(it&)?
The type of i is int&&, but i itself is an lvalue. So when you're calling fun(i), since i itself is an lvalue, the compiler will choose fun(int &).
If you want to invoke fun(int &&), you can use std::move to cast it to an rvalue
fun(std::move(i));
why do I need std::forward here since compiler already knows that i is int&& not int& but still calls fun(it&)?
Because i when used as/in an expression such as the call fun(i) is an lvalue. That is the value category of i when used as/in an expression is lvalue. Thus the call fun(i) selects the first overload(void fun(int&)).
On the other hand, the declared type of i is int&& i.e., an rvalue reference to int.
I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.
I've understood how std::move works and implemented my own version for practice only. Now I'm trying to understand how std::forward works:
I've implemented this so far:
#include <iostream>
template <typename T>
T&& forward_(T&& x)
{
return static_cast<T&&>(x);
}
/*template <typename T>
T&& forward_(T& x)
{
return static_cast<T&&>(x);
}*/
void incr(int& i)
{
++i;
}
void incr2(int x)
{
++x;
}
void incr3(int&& x)
{
++x;
}
template <typename T, typename F>
void call(T&& a, F func)
{
func(forward_<T>(a));
}
int main()
{
int i = 10;
std::cout << i << '\n';
call(i, incr);
std::cout << i << '\n';
call(i, incr2);
std::cout << i << '\n';
call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.
std::cout << "\ndone!\n";
}
Why must I provide the overloaded forward(T&) version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0 to call along with the incr3 function that takes an rvalue reference of type int&& normally doesn't need forward<T>(T&)?!
If I un-comment the forward_(T&) version it works fine!?
I'm still confused about: why if I only use the forward_(T&) version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)?
If I un-comment the version taking lvalue reference to T& and the one taking forwarding reference T&& then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&& never called!
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
I mean running the same code in the driver program I've shown above.
A T&& reference stops being a forwarding reference if you manually specify T (instead of letting the compiler deduce it). If the T is not an lvalue reference, then T&& is an rvalue reference and won't accept lvalues.
For example, if you do forward_<int>(...), then the parameter is an rvalue reference and ... can only be an rvalue.
But if you do forward_(...), then the parameter is a forwarding reference and ... can have any value category. (Calling it like this makes no sense though, since forward_(x) will have the same value category as x itself.)
It is clear that you wander why having two versions of std::forward; one takes an l-value reference to the type parameter T& and the other takes a universal reference (forwarding) to the type parameter. T&&.
In your case you are using forward_ from inside the function template call which has forwarding reference too. The problem is that even that function call called with an rvalue it always uses forward_ for an lvalue because there's no way that call can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&) is called.
Now you ask why there's second version taking forwarding reference?
It is so simple and as you may have already guessed: it is used for r-values (the values not the names of those objects).
Here is an example:
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
int main()
{
int i = 10;
forward_(i); // forward(T&) (1)
forward_(5); // forward(T&&) (2)
forward_("Hi"); // forward(T&) (3)
}
I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.
I don't understand very well the std::move function
template <class T>
typename remove_reference<T>::type&&
move(T&& a)
{
return a;
}
why remove_reference ?
could someone give me a simple explanation ?
Think about what happens if T is an lvalue reference, for example MyClass &. In that case, T && would become MyClass & &&, and due to reference collapsing rules, this would be transformed into MyClass & again. To achieve the right result, typename remove_reference<MyClass&>::type&& first removes any reference decorations from the type, so MyClass & is mapped to MyClass, and then the rvalue reference is applied to it, yielding MyClass &&.
Because rvalue reference to lvalue reference would decay to lvalue reference, and returing lvalue reference would have different semantics from those you would expect from move.
Edit:
Huh, why the downvote? Check out this code:
template < typename T > T&& func(T&& x) { return x; }
int main()
{
int x;
int &y = func(x);
}
Further reading: http://www.justsoftwaresolutions.co.uk/cplusplus/rvalue_references_and_perfect_forwarding.html