Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use std::forward<T> instead of static_cast<T&&>

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

  1. Should forward an lvalue as an lvalue
  2. Should forward an rvalue as an rvalue
  3. Should not forward an rvalue as an lvalue
  4. Should forward less cv-qualified expressions to more cv-qualified expressions
  5. Should forward expressions of derived type to an accessible, unambiguous base type
  6. 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.

like image 881
Curious Avatar asked Nov 12 '18 07:11

Curious


People also ask

Why to use std:: forward?

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).

What is forwarding reference in C++?

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.

Does Static_cast take time?

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.

How does std:: move work?

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.


1 Answers

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 for std::forward is similar to that for std::move, but whereas std::move unconditionally casts its argument to an rvalue, std::forward does it only under certain conditions. std::forward is a conditional cast. It casts to an rvalue only if its argument was initialized with an rvalue.
...
Given that both std::move and std::forward boil down to casts, the only difference being that std::move always casts, while std::forward only sometimes does, you might ask whether we can dispense with std::move and just use std::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.

like image 198
P.W Avatar answered Sep 28 '22 12:09

P.W