69

I always read that std::forward is only for use with template parameters. However, I was asking myself why. See the following example:

void ImageView::setImage(const Image& image){
    _image = image;
}

void ImageView::setImage(Image&& image){
    _image = std::move(image);
}

Those are two functions which basically do the same; one takes an l-value reference, the other an r-value reference. Now, I thought since std::forward is supposed to return an l-value reference if the argument is an l-value reference and an r-value reference if the argument is one, this code could be simplified to something like this:

void ImageView::setImage(Image&& image){
    _image = std::forward(image);
}

Which is kind of similar to the example cplusplus.com mentions for std::forward (just without any template parameters). I'd just like to know, if this is correct or not, and if not why.

I was also asking myself what exactly would be the difference to

void ImageView::setImage(Image& image){
    _image = std::forward(image);
}
BartoszKP
  • 32,105
  • 13
  • 92
  • 123
bweber
  • 2,976
  • 2
  • 20
  • 49
  • 8
    To really understand, watch this video: http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11 –  Mar 03 '15 at 09:36
  • 3
    Problem: Forward does _not_ return an lvalue reference or rvalue reference depending on it's parameter. In fact, it ignores the value-category of the parameter altogether. – Mooing Duck Mar 03 '15 at 17:36
  • 1
    Does this answer your question? [What's the difference between std::move and std::forward](https://stackoverflow.com/questions/9671749/whats-the-difference-between-stdmove-and-stdforward) – Antonio Mar 20 '21 at 21:13

3 Answers3

90

You cannot use std::forward without explicitly specifying its template argument. It is intentionally used in a non-deduced context.

To understand this, you need to really understand how forwarding references (T&& for a deduced T) work internally, and not wave them away as "it's magic." So let's look at that.

template <class T>
void foo(T &&t)
{
  bar(std::forward<T>(t));
}

Let's say we call foo like this:

foo(42);
  • 42 is an rvalue of type int.
  • T is deduced to int.
  • The call to bar therefore uses int as the template argument for std::forward.
  • The return type of std::forward<U> is U && (in this case, that's int &&) so t is forwarded as an rvalue.

Now, let's call foo like this:

int i = 42;
foo(i);
  • i is an lvalue of type int.
  • Because of the special rule for perfect forwarding, when an lvalue of type V is used to deduce T in a parameter of type T &&, V & is used for deduction. Therefore, in our case, T is deduced to be int &.

Therefore, we specify int & as the template argument to std::forward. Its return type will therefore be "int & &&", which collapses to int &. That's an lvalue, so i is forwarded as an lvalue.

Summary

Why this works with templates is when you do std::forward<T>, T is sometimes a reference (when the original is an lvalue) and sometimes not (when the original is an rvalue). std::forward will therefore cast to an lvalue or rvalue reference as appropriate.

You cannot make this work in the non-template version precisely because you'll have only one type available. Not to mention the fact that setImage(Image&& image) would not accept lvalues at all—an lvalue cannot bind to rvalue references.

Olivia Stork
  • 4,312
  • 4
  • 23
  • 39
Angew is no longer proud of SO
  • 156,801
  • 13
  • 318
  • 412
  • 1
    Ok, I think I understood that so far. What I was wondering: I can call a function `template void foo(T&& t)` with l-value references as well as with r-value references? And in case it's an lvalue reference T is deduced as `V&`, which means the arguments type becomes `(V& &&)`, which is basically an rvlaue reference to an lvalue reference (or what?)? So this is a point where the behaviour differs from non-template functions, where a function `void foo(MyType&& t)` can only be called with r-value references? – bweber Mar 03 '15 at 12:25
  • 4
    @user1488118 There are reference collapsing rules. `T && &&` collapses to `T &&`, all others (`T & &&` , `T && &` and `T & &`) collapse to `T &`. --- Yes, the deduction of `V&` is what allows you to call the function template with both lvalues and rvalues (not necessarily references). A non-template function taking `Image&&` can only be called with rvalues of type `Image`. – Angew is no longer proud of SO Mar 03 '15 at 12:37
  • Ok. If I declared my function as `template void foo(T& t)` what would T then be deduced to for calls like `foo(42)` or `int i = 42; foo(i);`? – bweber Mar 03 '15 at 12:54
  • 2
    `foo(i)` would deduce `T` as `int`. `foo(42)` [wouldn't compile](http://coliru.stacked-crooked.com/a/46e3f0077c495103), because 42 is an rvalue and cannot bind to a non-const lvalue reference. The special rule of using `V &` instead of `V` only applies when the type of the function parameter is `T &&` for a template parameter `T`. Anything else causes the rule to not apply. – Angew is no longer proud of SO Mar 03 '15 at 13:04
  • Ok. Just a question regarding the behaviour of `std::forward`: From what I understand from your post: a call `std::forward(i)` would have a return value of type `int&&`, `std::forward(i)` would have a return of type `int&`, and `std::forward(i)` would have a return of type `int&&`? If we now exchange `i` with the literal `42`, would it be the same? The documentation states: "[std::forward] Returns an rvalue reference to arg if arg is not an lvalue reference. If arg is an lvalue reference, the function returns arg without modifying its type." It doesn't say anything about . – bweber Mar 03 '15 at 14:12
  • 1
    @user1488118 Which "documentation"? Cplusplus.com? That site is not known for its accuracy, quite the opposite. Prefer [cppreference.com](http://en.cppreference.com/w/cpp/utility/forward). Either way, the only authoritative source of "documentation" is the standard, which specifies it as I've described in my answer. – Angew is no longer proud of SO Mar 03 '15 at 14:20
  • 1
    "std::forward will therefore cast to an lvalue of rvalue reference as appropriate." I think you meant to say: std::forward will therefore cast to an lvalue OR rvalue reference as appropriate. I only bring this up as I could see someone getting confused who doesn't understand how it works – bjackfly Jan 20 '16 at 00:10
  • @bjackfly Thanks, typo fixed. – Angew is no longer proud of SO Jan 20 '16 at 12:54
28

I recommend reading "Effective Modern C ++" by Scott Meyers, specifically:

  • Item 23: Understand std::move and std::forward.
  • Item 24: Distinguish universal references for rvalue references.

From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky. std::move’s attractions are convenience, reduced likelihood of error, and greater clarity.

rvalue-reference

This function accepts rvalues and cannot accept lvalues.

void ImageView::setImage(Image&& image){
    _image = std::forward(image);        // error 
    _image = std::move(image);           // conventional
    _image = std::forward<Image>(image); // unconventional
}

Note first that std::move requires only a function argument, while std::forward requires both a function argument and a template type argument.

Universal references (forwarding references)

This function accepts all and does perfect forwarding.

template <typename T> void ImageView::setImage(T&& image){
    _image = std::forward<T>(image);
}
Olivia Stork
  • 4,312
  • 4
  • 23
  • 39
Ron Tang
  • 1,392
  • 11
  • 19
7

You have to specify the template type in std::forward.

In this context Image&& image is always an r-value reference and std::forward<Image> will always move so you might as well use std::move.

Your function accepting an r-value reference cannot accept l-values so it is not equivalent to the first two functions.

Chris Drew
  • 14,004
  • 3
  • 30
  • 51