Usage of std::forward vs std::move

C++C++11MoveForward

C++ Problem Overview


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);
}

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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);
}

Solution 3 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbweberView Question on Stackoverflow
Solution 1 - C++Angew is no longer proud of SOView Answer on Stackoverflow
Solution 2 - C++Ron TangView Answer on Stackoverflow
Solution 3 - C++Chris DrewView Answer on Stackoverflow