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
&& &&->&&(rule1above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is
& &&->&(rule2above)
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.