I suppose when a universal reference parameter is matched with an rvalue reference argument, an rvalue reference argument is returned. However, my testing shows that the rvalue reference is turned into a lvalue reference by the universal reference function template. Why is it so?
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
T f1(T&&t) { //<-----this is a universal reference
cout << "is_lvalue reference:" << is_lvalue_reference<T>::value << endl;
cout << "is_rvalue reference:" << is_rvalue_reference<T>::value << endl;
cout << "is_reference:" << is_reference<T>::value << endl;
return t;
}
void f2(int&& t) {
cout << "f2 is_lvalue reference:" << is_lvalue_reference<decltype(t)>::value << endl;
cout << "f2 is_rvalue reference:" << is_rvalue_reference<decltype(t)>::value << endl;
cout << "f2 is_reference:" << is_reference<decltype(t)>::value << endl;
f1(t);
}
int main()
{
f2(5);
return 0;
}
In both GCC and VC++2010, this is the result:
f2 is_lvalue reference:0
f2 is_rvalue reference:1
f2 is_reference:1
is_lvalue reference:1
is_rvalue reference:0
is_reference:1
In other words, the parameter t
in f2
was an rvalue reference, but when passed to f1
, the parameter became a lvalue reference. Shouldn't it retain the rvalue-ness in f1
?
Calling f1(t)
, the argument is the expression t
. Not static_cast<decltype(t)>(t)
or something. Your examination of decltype(t)
has nothing to do with the call of f1(t)
.
The expression t
has type int
and value category lvalue . (A rule of thumb is that if you can take the address of an expression then it is an lvalue, and you can certainly write &t
). The "information" that a reference variable was originally declared as a reference is only visible via a decltype
examination.
Since f1
is called with an lvalue, T
is deduced to int&
.
NB. You possibly want f1
to also use decltype(t)
rather than T
, if you ever want to see is_rvalue_reference
being true in f1
. For rvalue arguments, T
deduces to a non-reference type, eg if you fix f2
by making it do f1(std::move(t));
then f1
's T
is int
and decltype(t)
in f1
is int&&
.
After studying the C++11 standard, I have a vague idea on what was going on after my f1(t);
in f2
. I describe it here to see if I got it right:
f2
, t
is an lvalue of type int&&
(not int
, this is an important difference) the call f1(t);
causes type to be deduced like this:
2.1 when T
in f1
is given an lvalue, it is deduced as a reference of that lvalue's type or int&& &
2.2 reference collapsing causes int&& &
to become int &
. This is the value of T
.
Since the parameter of f1
is declared as T&&
, the type of parameter t
in f1
is int & &&
. So reference collapsing occurs a second time to deduce the type of t
as int &
.
Hence T= int &
and type of parameter t
is int &
. ie parameter t
is an lvalue of type int &
Any comment?
use the std::forward to keep t as rvalue.
void f2(int&& t) {
//....
f1(std::forward<int>(t));
}
The t argument is passed as rvalue and deduced as type int. (see how the deduction is done here )
std::forward(t) returns a rvalue of type int&& so that calls f1(int&&)
without std::forward(t), f1(t) takes the argument of type int and calls f1(int&)
UPDATE: Please be aware of the difference between
is_lvalue_reference<T> and is_lvalue_reference<decltype<t>>.
In the template, T is deduced based on the function argument, whereas the value category of t is always lvalue in f2 and
forward<int>(t) and move(t)
are always rvalue.
#include <iostream>
#include <type_traits>
class A {};
template <typename T>
T f0(T& t) { //<-----this is a universal reference
std::cout<< "f0 lvalue"<<'\n';
// take T as template argument, it is type int, so not rvalue, not lvalue.
// std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
std::cout << "is_reference:" << std::is_reference<T>::value << std::endl;
return t;
}
template <typename T>
T f0(T&&t) { //<-----this is a universal reference
std::cout<< "f0 rvalue"<<'\n';
std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
std::cout << "is_reference:" << std::is_reference<T>::value << std::endl;
return t;
}
template <typename T>
T f1(T& t) { //<-----this is a universal reference
std::cout<< "f1 lvalue"<<'\n';
// take T as template argument, it is type int, so not rvalue, not lvalue.
// std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_reference:" << std::is_reference<decltype(t)>::value << std::endl;
return t;
}
template <typename T>
T f1(T&&t) { //<-----this is a universal reference
std::cout<< "f1 rvalue"<<'\n';
std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_reference:" << std::is_reference<decltype(t)>::value << std::endl;
return t;
}
void f2(int&&t) { //<-----this is a universal reference
std::cout<< "f2 rvalue"<<'\n';
std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
std::cout << "is_reference:" << std::is_reference<decltype(t)>::value << std::endl;
f1(std::forward<int>(t)); // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
f1(std::move(t)); // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
f1(t); // T is deduced as int for f(T&), type int is not rvalue, nor lvalue, t is lvalue
//if f1(T&) not exist, then f1(t) will call f1(T&&), T is deduced as int&, type int& is lvalue, t is lvalue
f0(std::forward<int>(t)); // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
f0(std::move(t)); // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
f0(t); // T is deduced as int for f(T&), type int is not rvalue, nor lvalue, t is lvalue
//if f0(T&) not exist, then f0(t) will call f0(T&&), T is deduced as int&, type int& is lvalue, t is lvalue
}
void test_rvalue()
{
f2(5);
std::cout << std::boolalpha;
std::cout << std::is_lvalue_reference<A>::value << '\n'; // A is not lvalue
std::cout << std::is_rvalue_reference<A>::value << '\n'; // A is not rvalue
std::cout << std::is_lvalue_reference<A&>::value << '\n'; // A& is lvalue
std::cout << std::is_rvalue_reference<A&&>::value << '\n'; // A&& is rvalue
std::cout << std::is_lvalue_reference<int>::value << '\n'; // int is not lvalue
std::cout << std::is_rvalue_reference<int>::value << '\n'; // int is not rvalue
std::cout << std::is_lvalue_reference<int&>::value << '\n'; // int& is lvalue
std::cout << std::is_rvalue_reference<int&&>::value << '\n'; // int&& is rvalue
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.