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);
42is an rvalue of typeint.Tis deduced toint.- The call to
bartherefore usesintas the template argument forstd::forward. - The return type of
std::forward<U>isU &&(in this case, that’sint &&) sotis forwarded as an rvalue.
Now, let’s call foo like this:
int i = 42;
foo(i);
iis an lvalue of typeint.- Because of the special rule for perfect forwarding, when an lvalue of type
Vis used to deduceTin a parameter of typeT &&,V &is used for deduction. Therefore, in our case,Tis 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.