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.
In C++ an lvalue is something that points to a specific memory location. On the other hand, a rvalue is something that doesn't point anywhere. In general, rvalues are temporary and short lived, while lvalues live a longer life since they exist as variables.
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
std::forward helps to implement perfect forwarding. This mechanism implies that objects passed to the function as lvalue expressions should be copied, and objects passed to the function as rvalue expressions should be moved. If you assign an rvalue reference to some ref variable, then ref is a named entity.
std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”
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
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)
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