Usage of std::forward vs std::move
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 typeint
. -
T
is deduced toint
. - The call to
bar
therefore usesint
as the template argument forstd::forward
. - The return type of
std::forward<U>
isU &&
(in this case, that'sint &&
) sot
is forwarded as an rvalue.
Now, let's call foo
like this:
int i = 42;
foo(i);
-
i
is an lvalue of typeint
. - Because of the special rule for perfect forwarding, when an lvalue of type
V
is used to deduceT
in a parameter of typeT &&
,V &
is used for deduction. Therefore, in our case,T
is deduced to beint &
.
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.
I recommend reading "Effective Modern C ++" by Scott Meyers, specifically:
-
Item 23: Understand
std::move
andstd::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);
}
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.