556

In perfect forwarding, std::forward is used to convert the named rvalue references t1 and t2 to unnamed rvalue references. What is the purpose of doing that? How would that affect the called function inner if we leave t1 & t2 as lvalues?

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}
user3100212
  • 101
  • 1
  • 5
Steveng
  • 5,681
  • 4
  • 17
  • 7
  • Remark: You can also write `std::forward(t1)` or `decltype(t1)(t1)`, see [c++ - Perfect forwarding in a lambda? - Stack Overflow](https://stackoverflow.com/questions/42799208/perfect-forwarding-in-a-lambda) – user202729 Jan 22 '22 at 13:32

7 Answers7

992

You have to understand the forwarding problem. You can read the entire problem in detail, but I'll summarize.

Basically, given the expression E(a, b, ... , c), we want the expression f(a, b, ... , c) to be equivalent. In C++03, this is impossible. There are many attempts, but they all fail in some regard.


The simplest is to use an lvalue-reference:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

But this fails to handle temporary values (rvalues): f(1, 2, 3);, as those cannot be bound to an lvalue-reference.

The next attempt might be:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

Which fixes the above problem because "const X& binds to everything", including both lvalues and rvalues, but this causes a new problem. It now fails to allow E to have non-const arguments:

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); 
f(i, j, k); // oops! E cannot modify these

The third attempt accepts const-references, but then const_cast's the const away:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

This accepts all values, can pass on all values, but potentially leads to undefined behavior:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); 
f(i, j, k); // ouch! E can modify a const object!

A final solution handles everything correctly...at the cost of being impossible to maintain. You provide overloads of f, with all combinations of const and non-const:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N arguments require 2N combinations, a nightmare. We'd like to do this automatically.

(This is effectively what we get the compiler to do for us in C++11.)


In C++11, we get a chance to fix this. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. So we have to find another way.

The solution is to instead use the newly added rvalue-references; we can introduce new rules when deducing rvalue-reference types and create any desired result. After all, we cannot possibly break code now.

If given a reference to a reference (note reference is an encompassing term meaning both T& and T&&), we use the following rule to figure out the resulting type:

"[given] a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR."

Or in tabular form:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

Next, with template argument deduction: if an argument is an lvalue A, we supply the template argument with an lvalue reference to A. Otherwise, we deduce normally. This gives so-called universal references (the term forwarding reference is now the official one).

Why is this useful? Because combined we maintain the ability to keep track of the value category of a type: if it was an lvalue, we have an lvalue-reference parameter, otherwise we have an rvalue-reference parameter.

In code:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

The last thing is to "forward" the value category of the variable. Keep in mind, once inside the function the parameter could be passed as an lvalue to anything:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

That's no good. E needs to get the same kind of value-category that we got! The solution is this:

static_cast<T&&>(x);

What does this do? Consider we're inside the deduce function, and we've been passed an lvalue. This means T is a A&, and so the target type for the static cast is A& &&, or just A&. Since x is already an A&, we do nothing and are left with an lvalue reference.

When we've been passed an rvalue, T is A, so the target type for the static cast is A&&. The cast results in an rvalue expression, which can no longer be passed to an lvalue reference. We've maintained the value category of the parameter.

Putting these together gives us "perfect forwarding":

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

When f receives an lvalue, E gets an lvalue. When f receives an rvalue, E gets an rvalue. Perfect.


And of course, we want to get rid of the ugly. static_cast<T&&> is cryptic and weird to remember; let's instead make a utility function called forward, which does the same thing:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    Wouldn't `f` be a function, and not an expression? – Michael Foukarakis Aug 27 '10 at 08:07
  • @sbi: Haha. :) <3 @mfukar: It's a function call expression. `f` itself could be anything "callable"; a function, function pointer, or function object. – GManNickG Aug 27 '10 at 08:07
  • Thanks for your comprehensive reply. Just a question, in deduce(1), x is type int&& or int? Because I think I read somewhere that if it is rvalue type, the T would be resolved to int. So with the extra &&, it would become int&& type. Could someone clarify that? http://thbecker.net/articles/rvalue_references/section_08.html – Steveng Aug 27 '10 at 09:31
  • 1
    Your last attempt is not correct with regard to the problem statement: It will forward const values as non-const, thus not forwarding at all. Also note that in the first attempt, the `const int i` will be accepted: `A` is deduced to `const int`. The failures are for the rvalues literals. Also note that for the call to `deduced(1)`, x is `int&&`, not `int` (perfect forwarding never makes a copy, as would be done if `x` would be a by-value parameter). Merely `T` is `int`. The reason that `x` evaluates to an lvalue in the forwarder is because named rvalue references become lvalue expressions. – Johannes Schaub - litb Aug 27 '10 at 11:40
  • Using the C++0x expression taxonomy: `x` evaluates to an *lvalue* in the forwarder and `static_cast(x)` evaluates to an *xvalue* in the forwarder. Lvalues and xvalues are called *glvalues*. *Prvalues* (literals, etc...) and xvalues are called *rvalues*. – Johannes Schaub - litb Aug 27 '10 at 11:50
  • 1
    Sorry for the slow reply. @Steveng: Yes, I was incorrect, as Johannes explained. @Johannes: Thanks for the fixes (and duh on my part for `A` being deduced to `const int` in the first part, just wasn't thinking.) I'll admit I haven't even read the new taxonomy or deduction rules. :S (Just did, not too bad.) Is this correct? – GManNickG Aug 28 '10 at 17:52
  • This answer is positively outstanding. If I could up-vote it multiple times I would, and thanks to @Johannes for the corrections. This honestly belongs as a wiki candidate. – WhozCraig Mar 30 '13 at 01:47
  • 6
    Is there any difference in using `forward` or `move` here? Or is it just a semantic difference? – David G Apr 01 '13 at 12:42
  • 45
    @David: `std::move` should be called without explicit template arguments and always results in an rvalue, while `std::forward` may end up as either. Use `std::move` when you know you no longer need the value and want to move it elsewhere, use `std::forward` to do that according to the values passed to your function template. – GManNickG Apr 01 '13 at 14:52
  • 6
    Thanks for starting with concrete examples first and motivating the problem; very helpful! – ShreevatsaR Jan 30 '15 at 20:59
  • 1
    This is an outstanding answer. It also makes me happy that Python just uses call by object and sad that I have to use C++. – Claudiu May 12 '15 at 17:08
  • Nice Explanation. Just came across this article which gives a good explanation http://thbecker.net/articles/rvalue_references/section_01.html – Dhaval Feb 18 '16 at 21:15
  • no need of other answers, this what Scott Meyers presented in his book and was nicely and greatly presented in this answer. Superb – Blood-HaZaRd Feb 17 '19 at 21:30
  • Where it says "When we've been passed an rvalue, T is A" - why is that? Why is T not A&&? The stated rationale for std::forward() seems like hinges on this, but I don't understand why the compiler would deduce T to be A, when it can easily deduce it to be A&& (thereby obviating the need for std::forward()). – Ziffusion Mar 10 '19 at 17:02
  • @Ziffusion: It's been long enough that now I'm fuzzy on the details, but IIRC it would break existing (pre-c++11) code. I can't conjure up an obvious example, maybe `template void check(const T&)`? – GManNickG Mar 12 '19 at 02:28
  • 1
  • @GManNickG What does "given the expression E(a, b, ... , c), we want the expression f(a, b, ... , c) to be equivalent." mean? How can I comprehend it? – John Jun 13 '20 at 08:41
  • @John: What it means is that writing out `f(a, b, ..., c)` behaves as if I had written `E(a, b, ..., c)` for all possible arguments. Take the first example, it fails this requirement because `f(1, 2, 3)` does not work but `E(1, 2, 3)` does. The linked paper is more formal. – GManNickG Jun 13 '20 at 18:11
  • @GManNickG Thank you for the clarification. One more question, does `std::forward` generate assembly? – John Jun 14 '20 at 00:32
  • @John It only changes the value category of the arguments, so I would expect no assembly is produced in an optimized build. However, only you can disassemble your program to find out. – GManNickG Jun 14 '20 at 19:10
  • @GManNickG One more question, which statement is right for deducing `int a;f(a);` : "since a is an lvalue, so `int(T&&)` equate to `int(int& &&)`" or "to make the `T&&` equate to `int&`, so `T` should be `int&`"? I prefer the latter. I am confused that which one is the reason, which one is the result. – John Jun 15 '20 at 14:30
  • @John From the example in the answer: `deduce(i); // deduce(int& &&) -> deduce(int&)`. Because a is an lvalue, T will be `int&`. This is irrespective any parameters. The fact the parameter type is then `T&&` -> `T& &&` -> `T&` is a choice made by the developer when they added `&&` to the type template. – GManNickG Jun 15 '20 at 19:58
  • Hi, @GManNickG thanks for you answer! But I'm confused by the last `okay`: `deduce(1); // okay, foo operates on x which has a value of 1` which I think is no okay as the argument type of foo is `int&` but in deduce type of x is `int&&`. And below that line you post `That's no good`, so what's the exactly meaning of `okay` and `no good` here? thanks. – roachsinai Jan 05 '21 at 14:11
  • @GManNickG ***Basically, given the expression `E(a, b, ... , c)`, we want the expression `f(a, b, ... , c)` to be equivalent.*** What are the relations between the said `E(a, b, ... , c)` and `f(a, b, ... , c)`? Could you please explain that in detail for me? – John Sep 14 '22 at 01:39
106

I think to have a conceptual code implementing std::forward can help with the understanding. This is a slide from Scott Meyers talk An Effective C++11/14 Sampler

conceptual code implementing std::forward

Function move in the code is std::move. There is a (working) implementation for it earlier in that talk. I found actual implementation of std::forward in libstdc++, in file move.h, but it is not at all instructive.

From a user's perspective, the meaning of it is that std::forward is a conditional cast to an rvalue. It can be useful if I am writing a function which expects either an lvalue or rvalue in a parameter and wants to pass it to another function as an rvalue only if it was passed in as an rvalue. If I did not wrap the parameter in std::forward, it would be always passed as a normal reference.

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

template<typename T>
void pass_through(T&& param) {
  overloaded_function(std::forward<T>(param));
}

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

Sure enough, it prints

std::string& version
std::string&& version

The code is based on an example from the previously mentioned talk. Slide 10, at about 15:00 from the start.

ololuki
  • 377
  • 1
  • 7
  • 14
user7610
  • 25,267
  • 15
  • 124
  • 150
  • 3
    Your second link has ended up pointing somewhere completely different. – Pharap May 04 '18 at 07:01
  • 3
    Wow, great explanation. I started from this video: https://www.youtube.com/watch?v=srdwFMZY3Hg, but after reading your answer, finally I feel it. :) – flamingo Nov 15 '20 at 21:45
50

In perfect forwarding, std::forward is used to convert the named rvalue reference t1 and t2 to unnamed rvalue reference. What is the purpose of doing that? How would that effect the called function inner if we leave t1 & t2 as lvalue?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

If you use a named rvalue reference in an expression it is actually an lvalue (because you refer to the object by name). Consider the following example:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

Now, if we call outer like this

outer(17,29);

we would like 17 and 29 to be forwarded to #2 because 17 and 29 are integer literals and as such rvalues. But since t1 and t2 in the expression inner(t1,t2); are lvalues, you'd be invoking #1 instead of #2. That's why we need to turn the references back into unnamed references with std::forward. So, t1 in outer is always an lvalue expression while forward<T1>(t1) may be an rvalue expression depending on T1. The latter is only an lvalue expression if T1 is an lvalue reference. And T1 is only deduced to be an lvalue reference in case the first argument to outer was an lvalue expression.

Community
  • 1
  • 1
sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • 2
    This is a kind of watered down explanation, but a very well done and functional explanation. People should read this answer first and then go deeper if desired – NicoBerrogorry Jun 27 '18 at 21:42
  • @sellibitze One more question, which statement is right when deducing int a;f(a):"since a is an lvalue, so int(T&&) equate to int(int& &&)" or "to make the T&& equate to int&, so T should be int&"? I prefer to the latter one. – John Jun 14 '20 at 05:29
13

How would that affect the called function inner if we leave t1 & t2 as lvalue?

If, after instantiating, T1 is of type char, and T2 is of a class, you want to pass t1 per copy and t2 per const reference. Well, unless inner() takes them per non-const reference, that is, in which case you want to do so, too.

Try to write a set of outer() functions which implement this without rvalue references, deducing the right way to pass the arguments from inner()'s type. I think you'll need something 2^2 of them, pretty hefty template-meta stuff to deduce the arguments, and a lot of time to get this right for all cases.

And then someone comes along with an inner() that takes arguments per pointer. I think that now makes 3^2. (Or 4^2. Hell, I can't be bothered to try to think whether const pointer would make a difference.)

And then imagine you want to do this for a five parameters. Or seven.

Now you know why some bright minds came up with "perfect forwarding": It makes the compiler do all this for you.

sbi
  • 219,715
  • 46
  • 258
  • 445
7

A point that hasn't been made crystal clear is that static_cast<T&&> handles const T& properly too.
Program:

#include <iostream>

using namespace std;

void g(const int&)
{
    cout << "const int&\n";
}

void g(int&)
{
    cout << "int&\n";
}

void g(int&&)
{
    cout << "int&&\n";
}

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

Produces:

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

Note that 'f' has to be a template function. If it's just defined as 'void f(int&& a)' this doesn't work.

Bill Chapman
  • 71
  • 1
  • 3
5

It may be worthwhile to emphasize that forward has to be used in tandem with an outer method with forwarding/universal reference. Using forward by itself as the following statements is allowed, but does no good other than causing confusion. The standard committee may want to disable such flexibility otherwise why don't we just use static_cast instead?

     std::forward<int>(1);
     std::forward<std::string>("Hello");

In my opinion, move and forward are design patterns which are natural outcomes after r-value reference type is introduced. We should not name a method assuming it is correctly used unless incorrect usage is forbidden.

colin
  • 101
  • 2
  • 4
  • I don't think that the C++ committee feels the onus is on them to use the language idioms "correctly", nor even define what "correct" usage is (though they can certainly give guidelines). To that end, while a person's teachers, bosses and friends may have a duty to steer them one way or another, I believe the C++ committee (and therefore the standard) does not have that duty. – SirGuy Dec 15 '17 at 15:23
  • Yeah, I just read [N2951](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html) and I agree that the standard committee has no obligation to add unnecessary limitations regarding the usage of a function. But the names of these two function templates (move and forward) are indeed a bit confusing seeing only their definitions in the library file or standard documentation (23.2.5 Forward/move helpers). The examples in the standard definitely help understand the concept, but it might be useful to add more remarks to make things a bit more clearer. – colin Dec 20 '17 at 06:33
1

From another viewpoint, when dealing with rvalues in a universal reference assignment, it may be desirable to preserve the type of a variable as it is. For example

auto&& x = 2; // x is int&&
    
auto&& y = x; // But y is int&    
    
auto&& z = std::forward<decltype(x)>(x); // z is int&&

Using std::forward, we ensured z exactly has the same type as x.

Moreover, std::forward doesn't affect lvalue references:

int i;

auto&& x = i; // x is int&

auto&& y = x; // y is int&

auto&& z = std::forward<decltype(x)>(x); // z is int&

Still z has the same type as x.

So, back to your case, if the inner function has two overloads for int& and int&&, you want to pass variables like z assignment not y one.

The types in the example can be assessed via:

std::cout<<is_same_v<int&,decltype(z)>;
std::cout<<is_same_v<int&&,decltype(z)>;

Sorush
  • 3,275
  • 4
  • 28
  • 42