Why is template parameter a reference in this case? - c++

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

Related

function template overload with rvaule reference as argument does not work?

The function template with && as argument seems cannot be overloaded, when input is not rvalue. See here as example:
template<typename A, typename B>
void test_tp_func(A&& a, B&& b)
{
std::cout<<"tp1(" << a << "," << b << ")\n";
}
template<typename A>
void test_tp_func(A&& a, int&& b)
{
std::cout<<"tp2(" << a << "," << b << ")\n";
}
int main()
{
test_tp_func(1, 2);
int i{10};
const int& ir = i;
test_tp_func(2, ir);
test_tp_func(2, std::move(i));
}
The output is:
tp2(1,2)
tp1(2,10)
tp2(2,10)
We can see the test_tp_func(2, ir); is not using the overload at all. How can I make sure it use the test_tp_func(A&& a, int&& b)? One way is to add enable_if_t to disable the original template when B is int. However, the original template test_tp_func(A&& a, B&& b) is defined in a file that is not in my control.
updated the example to actually use const&
How can you choose the rvalue overload? It would help if the argument was an rvalue which i is not.
It's not a prvalue (it's a named variable) and it isn't an xvalue either.
If you just want to intercept the case where B = int, why not just make the argument type int? Using int&& will only match rvalues by definition.
As discussed in comments, you can't simply replace the forwarding reference B&& with an rvalue reference int&& (or any other concrete type in place of int) because forwarding references get special treatment during argument deduction (see here and search for "forwarding reference", it's a special case).
You need two overloads: the const ref const int&, and the rvalue ref int&&, to cover the same cases as the original forwarding reference.

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

not understanding double forwarding of an lvalue - when passed by value

#include <iostream>
#include <vector>
#include <type_traits>
#include <utility>
using namespace std;
template <typename Func, typename... Args>
void proxy(Func f, Args&&... args) {
f(std::forward<Args>(args)...);
}
void real_func(vector<int> v) {
cout << "size: " << v.size() << endl;
}
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
int main()
{
vector<int> ints = {1, 2, 3};
multicast_func(ints);
return 0;
}
and the output is:
size: 3
size: 0
why isn't it 3, 3? at what point does this lvalue become an rvalue and gets moved-from?
std::forward is intended to be used with universal references.
The parameter of multicast_func is not an universal reference, so std::forward makes no sense:
void multicast_func(vector<int> v) {
proxy(real_func, std::forward<vector<int>>(v));
proxy(real_func, std::forward<vector<int>>(v));
}
In this case, it effectively acts like std::move (because the template parameter is not an (lvalue) reference).
The prototype for std::forward called in your code is:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
When called with non-reference type, it effectively makes an rvalue reference out of the argument, which than is moved from. std::vector is guaranteed to be empty after being moved from it, so size becomes 0.
at what point does this lvalue become an rvalue and gets moved-from?
At the 1st time proxy is invoked v is converted to an rvalue, and then gets moved-from when passed to real_func.
void multicast_func(vector<int> v) {
// the return type of std::forward is T&&, i.e. vector<int>&& here
// for functions whose return type is rvalue reference to objec, the return value is an rvalue
// that means v is converted to an rvalue and passed to proxy
proxy(real_func, std::forward<vector<int>>(v));
// v has been moved when passed to real_func as argument
proxy(real_func, std::forward<vector<int>>(v));
}
The usage in proxy is the general usage of std::forward; according to the argument is an lvalue or rvalue, the template parameter will be deduced as T& or T. For T& std::forward will return an lvalue, for T std::forward will return an rvalue, so the value category is preserved. When you specify the template argument solely such capacity is lost.
std::forward, when not given a reference type, will cast provided object to an rvalue. This means the first call to
proxy(real_func, std::forward<vector<int>>(v));
will make v an rvalue which means it will move it into real_func. The second call then uses that moved from object and you get the size of 0 since it has been emptied.
This makes sense if we look at the function. The version of std::forward you are calling is
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
Since you passed std::vector<int> for T that means it will return a std::vector<int>&&. So, even though v is an lvalue, it is converted to an rvalue. If you want to maintain the lvalue-ness of v then you need to use std::vector<int>&. That gives you std::vector<int>& && and reference colapse rules turns that into std::vector<int>& leaving you with an lvalue.
In the first call of proxy, the parameter vector<int> v(in function real_func) is move constructed from the rvalue, so v (in function multicast_func) has been empty.
But if you change the type of paremeter as cosnt vector<int> &, the result will be 3, 3. Because the move constrctor has not been called.
void real_func(const vector<int>& v) {
cout << "size: " << v.size() << endl;// the output is 3, 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&&.

Understading this perfect forwarding example

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.