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.