Short answer:
Because for std::forward
to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward
inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward
to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg
is declared as T&&
,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T
is a generic type, (likewise, in string s; auto && ss = s;
ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc
is being instantiated, specifically as following:
- If an rvalue object, which has the type
_T
or _T &
, is passed to someFunc
, T
will be deduced as _T &
(, yeah, even if the type of X
is just _T
, please read Meyers' artical);
- If an rvalue of type
_T &&
is passed to someFunc
,T
will be deduced as _T &&
Now, you can replace T
with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)
(, in fact, in clang 11's implementation it is static_cast<T &&>(para)
, which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc
, std::forward
literally IS NOT ABLE TO deduce the original type of arg
.
If you try to make the compiler do it, it will never be deduced as _T &&
(, yeah, even when arg
is bind to an _T &&
, it is still an lvaule obj inside someFunc
, hence can only be deduceed as _T
or _T &
.... you really should read Meyers' artical).
Last, why should you only use std::forward
inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move
. You simply DON'T NEED std::forward inside non-template context.