100

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
  • 34,786
  • 15
  • 102
  • 130
bweber
  • 3,772
  • 3
  • 32
  • 57
  • 11
    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
  • 2
    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
  • 1
    When it is not a template parameter, the && means an rvalue (and not a forwarding ref), and thus following the [binding rules (see the table here)](https://stackoverflow.com/questions/47734382/c-how-does-the-compiler-decide-between-overloaded-functions-with-reference-t/47736813#47736813) which means lvalue ref to `Image` cannot be bounded to `Image&&`. – Amir Kirsh Dec 16 '21 at 06:08

3 Answers3

129

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,660
  • 5
  • 27
  • 40
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    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
  • 7
    @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
  • Would it make sense to use the simplified version (using forward without templates) mentioned in the question if you know that the function will always receive only rvalue as parameters? – Abhishek Bhagate Dec 19 '21 at 19:25
  • @AbhishekBhagate As the answer says, there *is no such simplified version.* `std::forward` is (intentionally) implemented in a way which prevents template argument deduction on its argument - you *cannot* call it without specifying the template argument explicitly. – Angew is no longer proud of SO Dec 20 '21 at 07:32
  • @AngewisnolongerproudofSO From your post, I can understand that using forward with template argument helps in forwarding accordingly based on whether `T` is lvalue or rvalue. But consider, I have a case where the function takes only one type and that is known to be always an rvalue & the function simply forwards the parameter. Then I don't see why it isn't possible in this case to use a function that takes a parameter as `myType&& param` and use `anotherFun(forward(param))`. Doesn't this achieve the purpose desired in this case? Why would I need to use template argument in this case? – Abhishek Bhagate Dec 20 '21 at 09:44
  • 1
    @AbhishekBhagate If you're accepting rvalue references only, then the thing to use is not `std::forward`, but `std::move`. – Angew is no longer proud of SO Dec 23 '21 at 09:20
  • @AngewisnolongerproudofSO Thanks! I got the point – Abhishek Bhagate Dec 23 '21 at 12:48
38

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,660
  • 5
  • 27
  • 40
Ron Tang
  • 1,532
  • 12
  • 20
  • 4
    N.B. the official name for "universal reference" is now _forwarding reference_ – Jonathan Wakely Mar 03 '15 at 10:54
  • @JonathanWakely: _"the official name for "universal reference" is now forwarding reference"_ - I didn't get the memo. Since when? Where was it announced? Thanks. – Johann Gerell Mar 03 '15 at 11:43
  • @JonathanWakely: It's defined in this proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf) but is that accepted in a recent standard draft? – Johann Gerell Mar 03 '15 at 11:55
  • 3
    @JohannGerell Herb Sutter also talked about it on CppCon 2014, and the latest edition of Effective C++ mentions it as well, IIRR. – Angew is no longer proud of SO Mar 03 '15 at 12:13
  • @JonathanWakely There's a [tag:universal-reference] tag. Would you happen to have a link which we could use to update the wiki? – Angew is no longer proud of SO Mar 03 '15 at 12:14
  • @JohannGerell, yes, for example see the current draft, [N4296](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf) – Jonathan Wakely Mar 03 '15 at 12:54
  • @Angew, the N4164 link above? – Jonathan Wakely Mar 03 '15 at 12:57
  • 3
    Thanks, for reference, _forwarding reference_ is defined in §14.8.2.3 of [N4296](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf) – Johann Gerell Mar 03 '15 at 13:53
  • As a note, the universal reference example should _probably_ constrain the template parameter, to make sure it's always `Image`. Considering that the question was asked about C++11, the appropriate way to do so would be by adding something like, e.g., `static_assert(std::is_same::type, Image>::value, "Must be called with an Image.");`. – Justin Time - Reinstate Monica Jul 31 '23 at 21:30
8

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,926
  • 3
  • 34
  • 54