What is the use of forwarding an rvalue? [duplicate] - c++

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.

Related

Forwarding reference and argument deduction

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.

Second overload of std::foward (example on cppreference.com)

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.

Why doesn't my forward_ function work for rvalues?

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

Reference collapsing rules not applying as expected?

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.

Passing temporaries as non-const references in C++

I have the following piece of code, as an example dec_proxy attempts to reverse the effects of the increment operator upon the type that is executed in a complex function call foo - which btw I cannot change the interface of.
#include <iostream>
template<typename T>
class dec_proxy
{
public:
dec_proxy(T& t)
:t_(t)
{}
dec_proxy<T>& operator++()
{
--t_;
return *this;
}
private:
T& t_;
};
template<typename T, typename S, typename R>
void foo(T& t, S& s, R& r)
{
++t;
++s;
++r;
}
int main()
{
int i = 0;
double j = 0;
short k = 0;
dec_proxy<int> dp1(i);
dec_proxy<double> dp2(j);
dec_proxy<short> dp3(k);
foo(dp1,dp2,dp3);
//foo(dec_proxy<int>(i), <---- Gives an error
// dec_proxy<double>(j), <---- Gives an error
// dec_proxy<short>(k)); <---- Gives an error
std::cout << "i=" << i << std::endl;
return 0;
}
The problem is that for the various types I'd like to use dec_proxy I currently require creating a specialized instance of dec_proxy - it seems like a very messy and limited approach.
My question is: What is the correct way to pass such short-lived temporaries as non-const reference parameters?
Taking Stephen's advice, you should look at the answer to How come a non-const reference cannot bind to a temporary object? and simply add a member function that returns a reference dec_proxy, e.g.:
dec_proxy &ref() { return *this; }
and call foo:
foo(
dec_proxy<int>(i).ref(),
dec_proxy<double>(j).ref(),
dec_proxy<short>(k).ref());
I'm pretty sure that compiles.
Thanks to MSN, the solution:
I don't think it is correct by adding the function template template<typename T> dec_proxy_impl<T>& dec_proxy(T&t).
What it did is just cheating compiler. It will result in runtime error. The function foo requires the lvaue or lvalue reference. But template<typename T> dec_proxy_impl<T>& dec_proxy(T&t) doesn't return a valid lvalue reference. In the implementation, it creates a temporary object, and returns it. After the function call finishes, the temporary object will be destroyed. So the value reference passed into the function foo is wrong. Actually the referenced object has already been destroyed. The ++t;++s;++r are trying to access the invalid objects. The behavior is undefined.
The solution from MSN is correct. The life time of the object dec_proxy<int>(i) is from its declaration to the end of the function call. It makes sure the parameter in the function foo is valid.
What you try to do is to pass a rvalue (your new dec_facade<int>(i)) as an lvalue reference, which explains why it doesn't work.
If you compiler support it, you can use rvalue references, using && type modifier :
(Support for rvalue reference could be enabled by switching on C++0x or C++11 [partial] support)
template<typename T>
void foo(T& t)
{
++t;
}
template<typename T>
void foo(T&& t)
{
++t;
}
But that only one part of the problem. What you try to do, is to pre-increment a temporary value! That's non-sense, as it won't live after that call. Your object will be increment, then destroyed.
One other solution would be to remove the & from your function definition, which would permit it to accept any parameter. But that's perhaps not what you want.