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); }
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
The idiomatic use of std::forward is inside a templated function with an argument declared as a forwarding reference , where the argument is now lvalue , used to retrieve the original value category, that it was called with, and pass it on further down the call chain (perfect forwarding).
In C++11, std::move is a standard library function that casts (using static_cast) its argument into an r-value reference, so that move semantics can be invoked. Thus, we can use std::move to cast an l-value into a type that will prefer being moved over being copied. std::move is defined in the utility header.
What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.
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
.bar
therefore uses int
as the template argument for std::forward
.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
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With