I'm having trouble overloading a function to take a value either by const reference or, if it is an rvalue, an rvalue reference. The problem is that my non-const lvalues are binding to the rvalue version of the function. I'm doing this in VC2010.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void foo(const T& t)
{cout << "void foo(const T&)" << endl;}
template <class T>
void foo(T&& t)
{cout << "void foo(T&&)" << endl;}
int main()
{
vector<int> x;
foo(x); // void foo(T&&) ?????
foo(vector<int>()); // void foo(T&&)
}
The priority seems to be to deduce foo(x) as
foo< vector<int> & >(vector<int>& && t)
instead of
foo< vector<int> >(const vector<int>& t)
I tried replacing the rvalue-reference version with
void foo(typename remove_reference<T>::type&& t)
but this only had the effect of causing everything to resolve to the const-lvalue reference version.
How do I prevent this behaviour? And why is this the default anyway - it seems so dangerous given that rvalue-references are allowed to be modified, this leaves me with an unexpectedly modified local variable.
EDIT: Just added non-template versions of the functions, and they work as expected. Making the function a template changes the overload resolution rules? That is .. really frustrating!
void bar(const vector<int>& t)
{cout << "void bar(const vector<int>&)" << endl;}
void bar(vector<int>&& t)
{cout << "void bar(vector<int>&&)" << endl;}
bar(x); // void bar(const vector<int>&)
bar(vector<int>()); // void bar(vector<int>&&)
When you have a templated function like this you almost never want to overload. The T&&
parameter is a catch anything parameter. And you can use it to get any behavior you want out of one overload.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void display()
{
typedef typename remove_reference<T>::type Tr;
typedef typename remove_cv<Tr>::type Trcv;
if (is_const<Tr>::value)
cout << "const ";
if (is_volatile<Tr>::value)
cout << "volatile ";
std::cout << typeid(Trcv).name();
if (is_lvalue_reference<T>::value)
std::cout << '&';
else if (is_rvalue_reference<T>::value)
std::cout << "&&";
std::cout << '\n';
}
template <class T>
void foo(T&& t)
{
display<T>();
}
int main()
{
vector<int> x;
vector<int> const cx;
foo(x); // vector<int>&
foo(vector<int>()); // vector<int>
foo(cx); // const vector<int>&
}
In order for T&&
to bind to an lvalue reference, T
must itself be an lvalue reference type. You can prohibit the template from being instantiated with a reference type T
:
template <typename T>
typename std::enable_if<!std::is_reference<T>::value>::type foo(T&& t)
{
cout << "void foo(T&&)" << endl;
}
enable_if
is found in <utility>
; is_reference
is found in <type_traits>
.
The reason that the overload taking T&&
is preferred over the overload taking a T const&
is that T&&
is an exact match (with T = vector<int>&
) but T const&
requires a qualification conversion (const-qualification must be added).
This only happens with templates. If you have a nontemplate function that takes a std::vector<int>&&
, you will only be able to call that function with an rvalue argument. When you have a template that takes a T&&
, you should not think of it as "an rvalue reference parameter;" it is a "universal reference parameter" (Scott Meyers used similar language, I believe). It can accept anything.
Allowing a T&&
parameter of a function template to bind to any category of argument is what enables perfect forwarding.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With