Type deduction while using universal references - c++

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

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

Why is template parameter a reference in this case?

Stubled upon a weird behaviour when playing with std::forward. Here is a small example:
#include <type_traits>
#include <iostream>
template <typename A>
void func(A&&)
{
std::cout
<< std::is_pointer<A>::value
<< std::is_lvalue_reference<A>::value
<< std::is_rvalue_reference<A>::value
<< std::endl;
using B = typename std::remove_reference<A>::type;
std::cout
<< std::is_pointer<B>::value
<< std::is_lvalue_reference<B>::value
<< std::is_rvalue_reference<B>::value
<< std::endl;
}
int main()
{
int* p = nullptr;
func(p);
}
It prints 010 and 100, meaning A is a reference and not a pointer, while std::remove_reference<A> is a pointer as expected.
But why is it so? I thought that A would be a pointer and A&& a reference inside the function body. Also, what types are A& and A&& if this is the case?
This is how forwarding reference works; when being passed an lvalue, the template parameter A will be deduced as an lvalue-reference. For this case, it's int*&, i.e. an lvalue-reference to pointer. (After reference collapsing, the function parameter's type would be int*& too.)
When being passed an rvalue, A will be deduced as non-reference type. For example if you pass an rvalue with type int*, A will be deduced as int*. (Then the function parameter's type would be int*&&.)

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

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?"

Type not deduced to be r-value reference: why not?

Please consider the following code:
class CMyClass {};
template<class T>
void func(T&& param) {
if (std::is_same<CMyClass, std::decay<T>::type>::value)
std::cout << "param is a CMyClass\n";
if (std::is_same<T, CMyClass&>::value)
std::cout << "param is a CMyClass reference\n";
else if (std::is_same<T, CMyClass&&>::value)
std::cout << "param is a CMyClass r-value reference\n";
else if (std::is_same<T, const CMyClass&>::value)
std::cout << "param is a const CMyClass reference\n";
else if (std::is_same<T, const CMyClass&&>::value)
std::cout << "param is a const CMyClass r-value reference\n";
else if (std::is_same<T, const CMyClass>::value)
std::cout << "param is a constant CMyClass\n";
else if (std::is_same<T, CMyClass>::value)
std::cout << "param is a CMyClass\n";
else
std::cout << "param is not a CMyClass\n";
}
CMyClass mc3;
func(std::move(mc3));
The output from this little program is
param is a CMyClass
param is a CMyClass
Why has the type of mc3 not been deduced to be an r-value reference please?
I can't find a good dupe for this even though one must exist somewhere, sorry.
The deduction rules for:
template <class T>
void foo(T&& )
in the context of a call foo(expr) are:
If expr is an lvalue of type U, then T is deduced as U& and the type T&& is U&, due to reference collapsing.
If expr is an rvalue of type U, then T is deduced as U the type T&& is U&&, due to reference collapsing.
In your example, std::move(mc3) is an rvalue (specifically an xvalue) of type CMyClass. Hence, T is deduced as CMyClass. This check:
else if (std::is_same<T, CMyClass&&>::value)
std::cout << "param is a CMyClass r-value reference\n";
will almost never be true as T will never deduce as an rvalue reference type. It could be specifically provided as such:
func<CMyClass&&>(std::move(mc3));
but that's an unlikely usage. What you can do instead is check:
else if (std::is_same<T&&, CMyClass&&>::value)
// ~~~~
That will handle all cases where the argument is an rvalue. Indeed, if you simply always check for T&&, that will handle all of your cases properly.
Why has the type of mc3 not been deduced to be an r-value reference please?
If param is an rvalue, T is a non-reference inside func, and T&& is an rvalue reference. Here's an example that empirically shows what T means in the body of a function that takes a forwarding reference:
template <typename T>
void func(T&& x)
{
std::is_same<T, something>{}; // (0)
std::is_same<T&&, something>{}; // (1)
}
In the case of (0):
T is T when an rvalue is passed to func. (*)
T is T& for when an lvalue is passed to func.
In the case of (1):
T&& is T&& when an rvalue is passed to func.
T&& is T& when an lvalue is passed to func.
If you use std::is_same<T&&, CMyClass>::value, you should either get a T& or T&&.
(*): note that the term "is" is inaccurate - the various meanings of T inside func depend on template argument deduction and reference collapsing.
In short:
T is deduced as:
T& if x is an lvalue.
T otherwise.
Due to reference collapsing, T&& is:
T& if x is an lvalue. (T& && -> T&)
T&& otherwise. (T&& && -> T&&)

Why rvalue reference pass as lvalue reference?

pass() reference argument and pass it to reference, however a rvalue argument actually called the reference(int&) instead of reference(int &&), here is my code snippet:
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue" << std::endl;
}
template <typename T>
void pass(T&& v) {
reference(v);
}
int main() {
std::cout << "rvalue pass:";
pass(1);
std::cout << "lvalue pass:";
int p = 1;
pass(p);
return 0;
}
the output is:
rvalue pass:lvalue
lvalue pass:lvalue
For p it is easy to understand according to reference collapsing rule, but why the template function pass v to reference() as lvalue?
template <typename T>
void pass(T&& v) {
reference(v);
}
You are using a Forwarding reference here quite alright, but the fact that there is now a name v, it's considered an lvalue to an rvalue reference.
Simply put, anything that has a name is an lvalue. This is why Perfect Forwarding is needed, to get full semantics, use std::forward
template <typename T>
void pass(T&& v) {
reference(std::forward<T>(v));
}
What std::forward<T> does is simply to do something like this
template <typename T>
void pass(T&& v) {
reference(static_cast<T&&>(v));
}
See this;
Why the template function pass v to reference() as lvalue?
That's because v is an lvalue. Wait, what? v is an rvalue reference. The important thing is that it is a reference, and thus an lvalue. It doesn't matter that it only binds to rvalues.
If you want to keep the value category, you will have to do perfect forwarding. Perfect forwarding means that if you pass an rvalue (like in your case), the function will be called with an rvalue (instead of an lvalue):
template <typename T>
void pass(T&& v) {
reference(std::forward<T>(v)); //forward 'v' to 'reference'
}