Reputation: 181
When I have a code which looks like this:
template<class T>
void f_(const T& arg)
{
cout << "void f(const T& arg): Cannot modify\n";
}
template<class T>
void f_(T&& arg)
{
cout << "void f(T&& arg): Can modify\n";
}
and in main I call it:
int main()
{
MemoryBlock block;
f_(block);
f_(MemoryBlock());
return 0;
}
The output is:
"void f(T&& arg): Can modify\n";
"void f(T&& arg): Can modify\n";
But when I change this code to non-generic, that is instead of function templates I'll have regular functions,
void f(const MemoryBlock&)
{
cout << "In f(const MemoryBlock&). This version cannot modify the parameter.\n";
}
void f(MemoryBlock&&)
{
cout << "In f(MemoryBlock&&). This version can modify the parameter.\n";
}
the output is more "intuitive":
"In f(const MemoryBlock&). This version cannot modify the parameter.";
"In f(MemoryBlock&&). This version can modify the parameter.";
It looks to me that only by changing functions from being templates to non templates changes totally the deduction rules for rvalue references.
Will be really greateful if somebody would explain that to me.
Upvotes: 5
Views: 285
Reputation: 9335
T&&
can be called as universal/forwarding
reference.
reference collapsing rules:
template<typename T> void foo(T&&);
Here, the following apply:
In your case:
template<class T> void f_(T&& arg);
f_(block); //case 1
f_(MemoryBlock()); //case 2
In case 1:
T = MemoryBlock& then T&& becomes T& && ==> gives T&
In case 2:
T = MemoryBlock then T&& becomes T&& ==> gives T&&
For these both case
template<class T> void f_(T&& arg)
is the best choice for the compiler hence its taken instead of
template<class T>
void f_(const T& arg)
Upvotes: 1
Reputation: 3239
When you use T&&
, that's not an rvalue reference, that's a universal reference parameter. They're declared the same way, but they behave differently.
When you remove the template parameters, you're no longer in a deducible context and it's actually an rvalue reference: they bind only to rvalues, of course.
In a deducible context (that is when type deduction is taking place), T&&
can be either an rvalue reference or an lvalue reference. Universal references can bind to virtually all combinations (const
, const volatile
, etc.) and in your case: const T&
.
Now your train of thought was to be efficient and overload for rvalues and then lvalues, but what happens is that the universal reference overload is a better match when deducing template arguments. Therefore, it will be selected over the const T&
overload.
Usually, you only want to keep the universal reference function and pair it up with std::forward<T>()
to perfect forward the argument(s). This removes the need for your const T&
overload, since the universal reference version will take over.
Please note that just because you see &&
in a deducible context, it does not mean that it is an universal reference; the &&
needs to be appended to the type being deduced, so here's an example of something that is actually an rvalue reference:
template<class T>
void f_(std::vector<T>&& arg) // this is an rvalue reference; not universal
{
cout << "void f(T&& arg): Can modify\n";
}
Here's a great talk on the matter: https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11
Upvotes: 5