lvalue binding to rvalue reference - c++

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.

Related

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.

An rvalue reference can only bind to non-const rvalues? [duplicate]

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.

Why is std::forward necessary with forwarding references [duplicate]

This question already has answers here:
What are the main purposes of std::forward and which problems does it solve?
(7 answers)
Closed 6 years ago.
In a function template like this
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
Isn't x an rvalue reference inside foo, if foo is called with an rvalue reference? If foo is called with an lvalue reference, the cast isn't necessary anyway, because x will also be an lvalue reference inside of foo. Also T will be deduced to the lvalue reference type, and so std::forward<T> won't change the type of x.
I conducted a test using boost::typeindex and I get exactly the same types with and without std::forward<T>.
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>
using std::cout;
using std::endl;
template <typename T> struct __ { };
template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
os << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "\033[0m";
return os;
}
template <typename T>
void foo(T&& x) {
cout << prt_type<__<T>>{} << endl;
cout << prt_type<__<decltype(x)>>{} << endl;
cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
cout << endl;
}
int main(int argc, char* argv[])
{
foo(1);
int i = 2;
foo (i);
const int j = 3;
foo(j);
foo(std::move(i));
return 0;
}
The output of g++ -Wall test.cc && ./a.out with gcc 6.2.0 and boost 1.62.0 is
__<int>
__<int&&>
__<int&&>
__<int&>
__<int&>
__<int&>
__<int const&>
__<int const&>
__<int const&>
__<int>
__<int&&>
__<int&&>
Edit: I found this answer: https://stackoverflow.com/a/27409428/2640636 Apparently,
as soon as you give a name to the parameter it is an lvalue.
My question is then, why was this behavior chosen over keeping rvalue references as rvalues even when they are given names? It seems to me that the whole forwarding ordeal could be circumvented that way.
Edit2: I'm not asking about what std::forward does. I'm asking about why it's needed.
Isn't x an rvalue reference inside foo ?
No, x is a lvalue inside foo (it has a name and an address) of type rvalue reference. Combine that with reference collapsing rules and template type deduction rules and you'll see that you need std::forward to get the right reference type.
Basically, if what you pass to as x is a lvalue, say an int, then T is deduced as int&. Then int && & becomes int& (due to reference collapsing rules), i.e. a lvalue ref.
On the other hand, if you pass a rvalue, say 42, then T is deduced as int, so at the end you have an int&& as the type of x, i.e. a rvalue. Basically that's what std::forward does: casts to T&& the result, like a
static_cast<T&&>(x)
which becomes either T&& or T& due reference collapsing rules.
Its usefulness becomes obvious in generic code, where you may not know in advance whether you'll get a rvalue or lvalue. If you don't invoke std::forward and only do f(x), then x will always be a lvalue, so you'll be losing move semantics when needed and may end up with un-necessary copies etc.
Simple example where you can see the difference:
#include <iostream>
struct X
{
X() = default;
X(X&&) {std::cout << "Moving...\n";};
X(const X&) {std::cout << "Copying...\n";}
};
template <typename T>
void f1(T&& x)
{
g(std::forward<T>(x));
}
template <typename T>
void f2(T&& x)
{
g(x);
}
template <typename T>
void g(T x)
{ }
int main()
{
X x;
std::cout << "with std::forward\n";
f1(X{}); // moving
std::cout << "without std::forward\n";
f2(X{}); // copying
}
Live on Coliru
You really don't want your parameters to be automatically moved to the functions called. Consider this function:
template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}
Now you really don't want an automatic move to bar and an empty parameter to baz.
The current rules of requiring you to specify if and when to move or forward a parameter are not accidental.
I get exactly the same types with and without std::forward<T>
...no? Your own output proves you wrong:
__<int> // T
__<int&&> // decltype(x)
__<int&&> // std::forward<T>(x)
Without using std::forward<T> or decltype(x) you will get int instead of int&&. This may inadvertently fail to "propagate the rvalueness" of x - consider this example:
void foo(int&) { cout << "int&\n"; }
void foo(int&&) { cout << "int&&\n"; }
template <typename T>
void without_forward(T&& x)
{
foo(x);
// ^
// `x` is an lvalue!
}
template <typename T>
void with_forward(T&& x)
{
// `std::forward` casts `x` to `int&&`.
// vvvvvvvvvvvvvvvvvv
foo(std::forward<T>(x));
// ^
// `x` is an lvalue!
}
template <typename T>
void with_decltype_cast(T&& x)
{
// `decltype(x)` is `int&&`. `x` is casted to `int&&`.
// vvvvvvvvvvv
foo(decltype(x)(x));
// ^
// `x` is an lvalue!
}
int main()
{
without_forward(1); // prints "int&"
with_forward(1); // prints "int&&"
with_decltype_cast(1); // prints "int&&"
}
wandbox example
x being an r-value is NOT the same thing as x having an r-value-reference type.
R-value is a property of an expression, whereas r-value-reference is a property of its type.
If you actually try to pass a variable that is an r-value reference to a function, it is treated like an l-value. The decltype is misleading you. Try it and see:
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T> struct wrap { };
template<class T>
void bar(T &&value) { std::cout << " vs. " << typeid(wrap<T>).name() << std::endl; }
template<class T>
void foo(T &&value) { std::cout << typeid(wrap<T>).name(); return bar(value); }
int main()
{
int i = 1;
foo(static_cast<int &>(i));
foo(static_cast<int const &>(i));
foo(static_cast<int &&>(i));
foo(static_cast<int const &&>(i));
}
Output:
4wrapIRiE   vs. 4wrapIRiE
4wrapIRKiE vs. 4wrapIRKiE
4wrapIiE     vs. 4wrapIRiE (these should match!)
4wrapIKiE   vs. 4wrapIRKiE (these should match!)

Why does std::forward return static_cast<T&&> and not static_cast<T>?

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