I came across the following example while attempting to understand what std::forward does
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
The output of the program is
calling fn with rvalue: [lvalue][rvalue]
Now my question is how did we get lvalue first ? Here is my line of thought
in our main I called fn(0); Now 0 is rvalue. So the universal reference x is deduced to the following
void fn (int&& && x);
Now according to reference collapsing we would get
void fn (int&& x);
Thus making x behave like an rvalue.So if x is passed then rvalue overloaded method should be called. However it seems the other overloaded lvalue reference function is called. I would appreciate it if someone could clarify this
A named variable is NEVER an rvalue. It is invariably an lvalue. Rvalues are only pure expressions which don't have names.
int && i = int(0);
Here the expression int(0) is an rvalue, but the variable i itself is an lvalue, declared to be binding to an rvalue.
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'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 am trying to understand how lvalues bind to rvalue references. Consider this code:
#include <iostream>
template<typename T>
void f(T&& x) {
std::cout << x;
}
void g(int&& x) {
std::cout << x;
}
int main() {
int x = 4;
f(x);
g(x);
return 0;
}
While the call to f() is fine, the call to g() gives a compile-time error. Does this kind of binding work only for templates? Why? Can we somehow do it without templates?
Since T is a template argument, T&& becomes a forwarding-reference. Due to reference collapsing rules, f(T& &&) becomes f(T&) for lvalues and f(T &&) becomes f(T&&) for rvalues.
0x499602D2 has already answered your question; nevertheless, the following changes to your code might give further insights.
I have added a static_assert to f to check the deduced type:
#include <type_traits>
template<typename T>
void f(T&& x) {
static_assert(std::is_same<T&&, int&>::value,"");
std::cout << x;
}
The assert does not fail, so the type of x in f is eventually int& (in this particular example).
I have changed how g is called in main:
g(std::move(x));
Now the code compiles and the program works as expected and prints 44.
Hope this helps a bit in understanding rvalue references.
Let's have a function called Y that overloads:
void Y(int& lvalue)
{ cout << "lvalue!" << endl; }
void Y(int&& rvalue)
{ cout << "rvalue!" << endl; }
Now, let's define a template function that acts like std::forward
template<class T>
void f(T&& x)
{
Y( static_cast<T&&>(x) ); // Using static_cast<T&&>(x) like in std::forward
}
Now look at the main()
int main()
{
int i = 10;
f(i); // lvalue >> T = int&
f(10); // rvalue >> T = int&&
}
As expected, the output is
lvalue!
rvalue!
Now come back to the template function f() and replace static_cast<T&&>(x) with static_cast<T>(x). Let's see the output:
lvalue!
rvalue!
It's the same! Why? If they are the same, then why std::forward<> returns a cast from x to T&&?
The lvalue vs rvalue classification remains the same, but the effect is quite different (and the value category does change - although not in an observable way in your example). Let's go over the four cases:
template<class T>
void f(T&& x)
{
Y(static_cast<T&&>(x));
}
template<class T>
void g(T&& x)
{
Y(static_cast<T>(x));
}
If we call f with an lvalue, T will deduce as some X&, so the cast reference collapses X& && ==> X&, so we end up with the same lvalue and nothing changes.
If we call f with an rvalue, T will deduce as some X so the cast just converts x to an rvalue reference to x, so it becomes an rvalue (specifically, an xvalue).
If we call g with an lvalue, all the same things happen. There's no reference collapsing necessary, since we're just using T == X&, but the cast is still a no-op and we still end up with the same lvalue.
But if we call g with an rvalue, we have static_cast<T>(x) which will copy x. That copy is an rvalue (as your test verifies - except now it's a prvalue instead of an xvalue), but it's an extra, unnecessary copy at best and would be a compilation failure (if T is movable but noncopyable) at worst. With static_cast<T&&>(x), we were casting to a reference, which doesn't invoke a copy.
So that's why we do T&&.
I am watching Scott Meyer's video "The Universal Reference/Overloading Collision Conundrum", where he gives an example of what not to do:
class MessedUp {
public:
template<typename T>
void doWork(const T& param) { std::cout << "doWork(const T& param)" << std::endl; }
template<typename T>
void doWork(T&& param) { std::cout << "doWork(T&& param)" << std::endl; }
};
.... //somewhere in the main
MessedUp m;
int w = 10;
const int cw = 20;
m.doWork(cw); // calls doWork(const T& param) as expected
m.doWork(std::move(cw)); // Calls doWork(T&& param)
I am curious as to why compiler chose doWork(T&& param) rather than doWork(const T& param) during Template Overload resolution. As far as I know, const objects can't be moved.
After template type deduction and substitution, the two overloads become:
//template<typename T>
void doWork(const int& param) { std::cout << "doWork(const T& param)" << std::endl; }
//template<typename T>
void doWork(const int&& param) { std::cout << "doWork(T&& param)" << std::endl; }
Note how T in the second overload has been deduced to const int.
Now, what happens is normal overload resolution: We compare the implicit conversion sequences required to convert the argument expression std::move(cw) (which is an xvalue of type const int) to the parameter types. Both rank as Exact Matches, so we have to look at the tie-breakers in [over.ics.rank]/3 and compare the two implicit conversion sequences S1 and S2 (a reference binding here is a conversion sequence):
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
[...]
S1 and S2 are reference bindings [...], and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.
As an xvalue is an rvalue (and a glvalue), this point applies, and the second overload is chosen.
&& does not mean move, it means rvalue reference. rvalue references will only bind to temporary(anonymous) objects, or objects cast to appear to be temporary objects by functions like std::move or std::forward, or objects automatically marked by the compiler to be temporary like locals returned from function on simple return X; lines.
You can have an rvalue reference to a const object. When this happens, you cannot move (unless mutable state can be moved), but it is still an rvalue reference.
Now, T&& can bind to an lvalue reference if T is an lvalue reference, so in type deduction context T&& can be called a universal reference. So one of the real problems with the above design is that m.doWork(w) will also bind to T&& with T=int&.
In overload resolution, a function that takes a template<typename T> void foo(T&&) with T=foo& be considered to be a worse match than template<typename T> void foo(T&) with T=foo if everything else is equal: but in your case, there is no T such that foo(T const&) is a foo(T const&&).
&& are useful to determine temporary rvalues from non-rvalue. So, you can steal the resource safely.
When you use std::move it casts the type to a rvalue and compiler will uses && overload.
What is happening is that doWork(T&& param) is being called with T = const int, because that is a perfect match (instead of a conversion to lvalue).
If you had trued to move the object, it would indeed have failed because const objects can't be moved.