Why const double && doesn't work for lvalue reference? - c++

Explain me, please, how it works?
Why double && works for lvalue and rvalue?
And why const double && don't work for lvalue?
template <typename U>
void function(U& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45); //error
}
error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’ function(45);
////////////////////////////////////////////////
template <typename U>
void function(const U& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45);
}
////////////////////////////////////////////////
template <typename U>
void function(U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45);
}
////////////////////////////////////////////////
template <typename U>
void function(const U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1); // error
function(45);
}
error: cannot bind ‘int’ lvalue to ‘const int&&’ function(var1);

double && does not work for lvalues. However, you don't have double && in your code, you have U && for a deduced U. That is special syntax for a so-called forwarding reference.
There's a special rule in C++ template deduction rules which says that when the parameter type is T&& where T is a deduced type, and the argument is an lvalue of type X, then X& is used instead of X for type deduction.
In your case, this means U is deduced to be int&, so the type of parameter var is collapsed from "int& &&" into int &.
You can learn more about this mechanism by searching for terms such as "forwarding reference" (historically called "universal reference") or "perfect forwarding."

Let's summarize the rules at first.
lvalues can be bound to lvalue-reference
lvalues can't be bound to rvalue-reference
rvalues can be bound to lvalue-reference to const
rvalues can't be bound to lvalue-reference to non-const
rvalues can be bound to rvalue-reference
lvalues can be bound to forwarding-reference (and preserve its value category)
rvalues can be bound to forwarding-reference (and preserve its value category)
The 1st case is lvalue-reference to non-const, then
function(var1); // rule#1 -> fine
function(45); // rule#4 -> fail
the 2nd case is lvalue-reference to const, then
function(var1); // rule#1 -> fine
function(45); // rule#3 -> fine
the 3rd case is forwarding-reference, then
function(var1); // rule#6 -> fine
function(45); // rule#7 -> fine
the 4th case is rvalue-reference, then
function(var1); // rule#2 -> fail
function(45); // rule#5 -> fine
It's worth noting the difference between rvalue-reference and forwarding-reference:
(emphasis mine)
function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template

In your example, function(var1) is going to deduce var1 usage as lvalue. To make it compatible with rvalue-based template, you have to help your compiler a bit:
template <typename U>
void function(const U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(std::move(var1)); // let convert lvalue to rvalue
function(45);
}
P.S. And yes, you should always ask yourself "do I really need that rvalue reference, or I'm perfectly fine with that old good lvalue ref?"

Related

C++ Template explicit rvalue type

#include <iostream>
using namespace std;
namespace mine {
template <typename T>
struct remove_rval {
using type = T;
};
template <typename T>
struct remove_rval<T&&> {
using type = T;
};
template <typename T>
void g(const T& = typename remove_rval<T>::type())
cout << __PRETTY_FUNCTION__ << endl;
}
}
int main()
{
mine::g<int&&>(); // doesn't work, because of explicit template?
const int& i2 = mine::remove_rval<int&&>::type(); // works, sanity check
return 0;
}
The function template I wrote fails to compile. From my understanding of c++, you can assign an rvalue to a constant lvalue reference. But, in this situation, it is like the deduced type disregards the 'const' qualifier when assigning the function default value. Why is this?
From dcl.ref/p6:
If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.decltype]) denotes a type TR that is a reference to a type T, an attempt to create the type lvalue reference to cv TR creates the type lvalue reference to T, while an attempt to create the type rvalue reference to cv TR creates the type TR.
Thus in your example, when T = int&& :
const T& collapses to T&(which isint&) and not const T&(which is const int&) according to the above quoted statement. And since we can't bind an rvalue like remove_rval<T>::type() to a non-const lvalue reference, you get the mentioned error.
Thus, even though the form of the function parameter in g is a reference to const T aka const lvalue reference(i.e., const T&), the first call to g instantiates g with a reference to non-const T aka non-const lvalue reference(i.e., T& =int&) as the parameter:
template<>
void g<int &&>(int&)
{
//operator<< called here
}
And since the parameter is int&, we cannot bind an rvalue like remove_rval<int&&>::type() to that parameter.
void g(const T& = typename remove_rval<T>::type())
Reference collapsing rules make const T& equal to U& when T = U&&. Lvalue-references can't bind to temporaries like remove_rval<T>::type(). You can instead simply pass int as a template argument and the parameter's type will correctly be const int&.

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

Type deduction while using universal references

Can someone help me to understand why the output of the following code
template< typename T >
void check()
{
std::cout << "unknow type" << std::endl;
}
template<>
void check<int>()
{
std::cout << "int" << std::endl;
}
template<>
void check<int&>()
{
std::cout << "int&" << std::endl;
}
template<>
void check<int&&>()
{
std::cout << "int&&" << std::endl;
}
template< typename T >
void bar( T&& a )
{
check<T>();
}
int main()
{
bar(0);
int a = 0;
bar( a );
}
is
int
int&
and not
int&&
int&
From my point of view, it seems more intuitive that an r-value reference remains as an r-value reference and an l-value reference an l-value reference, However, it seems that only l-value references remains as l-value references and r-values become non-reference values.
What is the motivation/idea behind this?
bar(0); calls the specialization bar<int>(int&&) i.e. T is deduced as int, so check<T>() is check<int>(). The parameter type is T&& which is int&&, but that's the type of the parameter, not the type T.
This is entirely consistent with non-forwarding references. If you define:
template<typename T> void baz(T&);
and you call it with an lvalue of type int then T is deduced as int, not int&
The only thing that's special about forwarding references like your example uses is that for T&& the type can be deduced as an lvalue reference, call it R, in which case the parameter type is R&& which is the same as add_rvalue_reference_t<R> which is just R. So for the call bar(a) you call the specialization bar<int&>(int&) i.e. T is deduced as int&
When you call bar<int&&>(0) with an explicit template argument list there is no argument deduction, and so T is substituted by int&&, so the parameter type T&& is add_rvalue_reference_t<int&&> which is just int&&.

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

Why does std::move() not work without _Remove_reference?

_Remove_reference exists for, as you know, converting T& to T or T&& to T.
I made the following code in a playful mood, it doesn't work at all as I expected, but have no idea why.
template<class _Ty>
struct _Remove_reference
{ // remove reference
typedef _Ty _Type;
static void func(){ cout << "1" << endl; }
};
// template<class _Ty>
// struct _Remove_reference<_Ty&>
// { // remove reference
// typedef _Ty _Type;
// static void func(){ cout << "2" << endl; }
// };
//
// template<class _Ty>
// struct _Remove_reference<_Ty&&>
// { // remove rvalue reference
// typedef _Ty _Type;
// static void func(){ cout << "3" << endl; }
// };
template<class _Ty> inline
typename _Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
typename _Remove_reference<_Ty>::func();
return ((typename _Remove_reference<_Ty>::_Type&&)_Arg);
}
int main(){
int a1= 3;
int&& a2 = move(a1); // can't convert the a1 to int&&
return 0;
}
I guess it's all about reference collapsing rules, and template argument deduction but I am confused. my curiosity about this has to be shattered for me to sleep tight.
Thanks in advance.
Given
template<class _Ty> inline
typename _Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
When you do move(a1), _Ty is deduced as int&. _Ty&& would thus still be int& due to the reference collapsing rules, so you need to remove the reference to get int before you can make it an rvalue reference.
This is a special case of the template argument deduction rules. If you have a function argument which is T&& where T is a template parameter, then if you pass an lvalue (i.e. a named object or an lvalue reference) to the function then T is deduced as X& where X is the type of the lvalue object. If you pass an rvalue (a temporary or an rvalue reference) to the function then T is just deduced as X.
e.g. move(a1) deduces _Ty to be int&, whereas move(42) deduces _Ty to be int.
BTW: I guess you took this code from your compiler's standard library --- names decorated with a leading underscore and a capital letter _Like _This are reserved for the compiler and standard library implementation.