When given code of the following structure
template <typename... Args>
void foo(Args&&... args) { ... }
I've often seen library code use static_cast<Args&&>
within the function for argument forwarding. Typically, the justification for this is that using a static_cast
avoids an unnecessary template instantiation.
Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>
, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)
- When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is
&& &&
->&&
(rule1
above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&
->&
(rule2
above)
This is essentially getting foo()
to forward the arguments to bar()
in the example above. This is the behavior you would get when using std::forward<Args>
here as well.
Question - why use std::forward
in these contexts at all? Does avoiding the extra instantiation justify breaking convention?
Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward
should behave "correctly". These were
- Should forward an lvalue as an lvalue
- Should forward an rvalue as an rvalue
- Should not forward an rvalue as an lvalue
- Should forward less cv-qualified expressions to more cv-qualified expressions
- Should forward expressions of derived type to an accessible, unambiguous base type
- Should not forward arbitrary type conversions
(1) and (2) were proven to work correctly with static_cast<Args&&>
above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.
Note: I personally prefer to use std::forward
, but the justification I have is purely that I prefer to stick to convention.
std::forward
because they want the library be self-contained, and don't want to use standard library. They will also implement their ownstd::move
, etc. – DervishT&
- wandbox.org/permlink/hPucHiFB2pwh53Ox – Involutionstd::forward
andstd::move
are for readability, you could usestatic_cast
instead to get the same behaviour – Paddlestd::move
was introduced to make the proposal more palatable. Then it stuck. I would imagine the same goes forstd::forward
. – Hanserstd::forward
has shenanigans to generate a compile error if you don't explicitly specify the the template parameter (which would lead to undesirable behaviour with no warning) – Paddlestd::forward
could hurt readability in some non-typical use. For example, in the implementation ofstd::function::operator()
, clang usesstd::forward
to dostatic_cast<T&&>
on arguments that aren't actually deduced, which is quite confusing. – Dervishstatic_cast<T&&>
is not aforward
/move
? – Solarstd::forward
. I don't seestd::forward
as "forward deduced arguments as if calling the underlying function directly with the argument", but as "preserve lvalue/rvalue-ness" – Gona