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)
&& &&
-> &&
(rule 1
above) & &&
-> &
(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
(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.
The idiomatic use of std::forward is inside a templated function with an argument declared as a forwarding reference , where the argument is now lvalue , used to retrieve the original value category, that it was called with, and pass it on further down the call chain (perfect forwarding).
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
static_cast MAY take time at run-time. For example, if you convert int to float then work is required. Usually casting pointers does not require any run-time cost.
std::move is a cast. It takes any value as argument and returns that same value in the xvalue category. And a value of type T and category xvalue is denoted thus: T&& . The move operation itself is performed by one of the constructors of the object to which it moves.
Scott Meyers says that std::forward
and std::move
are mainly for convenience. He even states that std::forward
can be used to perform the functionality of both std::forward
and std::move
.
Some excerpts from "Effective Modern C++":
Item 23:Understand std::move and std::forward
...
The story forstd::forward
is similar to that forstd::move
, but whereasstd::move
unconditionally casts its argument to anrvalue
,std::forward
does it only under certain conditions.std::forward
is a conditional cast. It casts to anrvalue
only if its argument was initialized with anrvalue
.
...
Given that bothstd::move
andstd::forward
boil down to casts, the only difference being thatstd::move
always casts, whilestd::forward
only sometimes does, you might ask whether we can dispense withstd::move
and just usestd::forward
everywhere. From a purely technical perspective, the answer is yes:std::forward
can do it all.std::move
isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.
...std::move
’s attractions are convenience, reduced likelihood of error, and greater clarity...
For those interested, comparison of std::forward<T>
vs static_cast<T&&>
in assembly (without any optimization) when called with lvalue
and rvalue
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With