Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect forwarding and templates

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.

like image 243
Artur Avatar asked Oct 16 '15 14:10

Artur


People also ask

What are Lvalues and Rvalues in C++?

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.

What is forwarding reference in C++?

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.

What is the purpose of std :: forward?

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.

What is STD forward?

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.”


2 Answers

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

like image 172
bku_drytt Avatar answered Oct 07 '22 20:10

bku_drytt


T&& can be called as universal/forwarding reference.
reference collapsing rules:

  1. A& & becomes A&
  2. A& && becomes A&
  3. A&& & becomes A&
  4. A&& && becomes A&&

template<typename T> void foo(T&&);

Here, the following apply:

  1. When foo is called on an lvalue of type A, then T resolves to A& and hence, by the reference collapsing rules above, the argument type effectively becomes A&.
  2. When foo is called on an rvalue of type A, then T resolves to A, and hence the argument type becomes A&&.

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)
like image 21
Praveen Avatar answered Oct 07 '22 21:10

Praveen