9

From what I understand, std::forward<T>(x) is equivalent to static_cast<T&&>(x).

But from what I saw, static_cast<T>(x) seems to do the same thing, as can be seen in the following code

My question is therefore why std::forward<T> is implemented as static_cast<T&&>(x), and not static_cast<T>(x), if both have the same effect?

Mikrosaft
  • 195
  • 1
  • 8

3 Answers3

13

Because perfect forwarding allows to pass both r-value references and l-value references. This is done via reference collapsing:

T = int    --> T&& = int&&
T = int&   --> T&& = int& && = int&
T = int&&  --> T&& = int&& && = int&&

In your example with static_cast<T> you're simply losing r-value references. It works fine for primitive types (because passing int is usually copying a CPU register value), but awful for complex types because it leads to creating temporary object through copy ctors.

myaut
  • 11,174
  • 2
  • 30
  • 62
  • I just checked it, and it is in fact calls the copy constructor for rvalue's. But why? I read that if we call func() with an rvalue, T is deduced to be int&&, so static_cast(x) would become static_cast(x). So why it creates a copy? – Mikrosaft Sep 27 '15 at 09:56
  • 2
    `static_cast` can return a reference: the answer above implies it cannot. Can you clarify that the problem only occurs in one "case"? – Yakk - Adam Nevraumont Sep 27 '15 at 09:58
  • @Mikrosaft: no, `T` cannot be deduced to `int&&`, it only can be either `int` or `int&`. – myaut Sep 27 '15 at 10:05
  • @myaut It makes perfect sense now. Thank you! – Mikrosaft Sep 27 '15 at 10:10
2

If T&& is an rvalue reference, then T is a value, then static_cast<T> makes a copy not a rvalue reference.

That copy will bind to rvalue references (just like the reference), but copy/move ctors could be needlessly called, and it is not a candidate for elision.

static_cast<T&&> will meanwhile just cast to an rvalue reference.

They are otherwise identical.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

I agree with Yakk, but it's worse than that.

void foo(const std::vector<int> &vec);

template<typename T>
void callFoo(T &&data)
{
    foo(static_cast<T>(data));
}

int main()
{
    callFoo(std::vector<int>{/*...*/});
}

This will always create a copy. The vector is not moved because data, as an expression, is an lvalue of type std::vector<int>, even though the substituted type is std::vector<int>&&. Note that T is std::vector<int>, not std::vector<int> &&. Moral of the story: Use the standard library, it does the right thing and the name forward also captures the intention far better than static_cast<something>.

Arne Vogel
  • 6,346
  • 2
  • 18
  • 31
  • Remark: when either [xvalue or prvalue](https://stackoverflow.com/q/3601602/5267751) is used as the argument `T` is `std::vector` and `T&&` (or `decltype(data)`) is `std::vector&&`. So `static_cast(data)` can be used, which is occasionally useful, see e.g. [c++ - Perfect forwarding in a lambda? - Stack Overflow](https://stackoverflow.com/q/42799208/5267751) – user202729 Jan 22 '22 at 13:43