Why use std::forward<T> instead of static_cast<T&&>
c++, c++17, forwarding, rvalue-reference, templates
Solution
`forward` expresses the intent and it may be safer to use than `static_cast`: `static_cast` considers conversion but some dangerous and supposedly non-intentional conversions are detected with `forward`:
struct A{
A(int);
};
template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}
template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}
void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}
Example of error message: compiler explorer link
How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.
Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?
When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:
template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
where `foo_1`,`foo_2`,`foo_3` are templates, the instantiation of `foo` will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like `forward` is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.
So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.
So, in my opinion using `static_cast` in place `forward` to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.
Problem
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 `&& &&` -> `&&` (rule `1` above) - When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is `& &&` -> `&` (rule `2` 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.