0

Consider this code:

template<typename T>
void foo(T&& param){ //In this case && is called universal reference
    std:string tmp = std::forward<string>(param);
}

My question is if universal reference type can be deduced why do I still need to call forward ?
Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.

My second question is about reference collapsing rules:

  1. A& && becomes A&
  2. A&& && becomes A&&

so according this rules and taking in account universal reference why std::forward signature can't be as follows:

template<class T> 
T&& forward(T&& arg){
    return static_cast<T&&>(arg);
}

According to the rules from above if T's type is rvalue reference it will collapse to rvalue reference , if T's type is lvalue reference it will collapse to lvalue reference.
So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160

1 Answers1

2

My question is if universal reference type can be deduced why do I still need to call forward ?

Because as soon as you give a name to the parameter param it is an lvalue, even if the function was called with an rvalue, so it wouldn't be forwarded as an rvalue unless you use forward<T>

Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.

Because param is an lvalue. To restore the value category of the argument passed to foo you need to cast it back to string& or string&& which means you need to know the type T was deduced as, and use forward to do the cast.

So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?

It was changed by http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html

There is lots of background info in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html

The problem with your suggested version is that if you say forward<string> then the parameter T is not deduced so doesn't function as a forwarding reference, which means that T&& can't bind to an lvalue, and it needs to be able to bind to an lvalue in order for forward<string>(param) to work, because param is an lvalue there.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • According to your answer foo's param is an lvalue when you call forward with lvalue you get lvalue. So how rvalue type is preserved here ? BTW according to Scott Meyers foo's signature implies that param is universal reference. – Alejandro Freeman Dec 10 '14 at 19:50
  • The rvalue-ness is lost inside the body of `foo`, because `param` is an lvalue there, but then you restore the rvalue type using `forward`. – Jonathan Wakely Dec 10 '14 at 19:53
  • And they are called [forwarding references](http://isocpp.org/files/papers/N4164.pdf) now, not universal references. – Jonathan Wakely Dec 10 '14 at 19:55
  • So if forward manages to "restore" the correct type of param which is preserved within foo's body why in my example tmp's c'tor can't do implicitly the same ? – Alejandro Freeman Dec 10 '14 at 20:01
  • Because you have to explicitly tell forward the type you want it to cast to, i.e. you must call `forward(param)` not just `forward(param)`. The explicit type there is **explicit** and cannot be deduced implicitly. If you don't use forward to restore the rvalue-ness then it is lost, it can't be restored by another function that gets passed an lvalue, it has no way to know that is was an rvalue two levels up the stack. – Jonathan Wakely Dec 10 '14 at 20:03
  • I think you misunderstood my last question I'll clarify it. Why compiler can preserve the correct type of param in foo's body but cannot implicitly call the correct string's c'tor (move c'tor or copy c'tor) why does it need help from STL ?! – Alejandro Freeman Dec 10 '14 at 20:09
  • (This has nothing to do with the STL! That is a library from the 1990s, it has nothing to do with move semantics or perfect forwarding.) And I think you've misunderstood my answers. `param` is an lvalue. Please read that again and again until you understand it. If you say `std::string tmp = param;` you are copying **an lvalue**. How is the compiler supposed to know if you want it to be treated as an rvalue? Guessing when you want to move instead of copying would be risky and might cause bugs. So you are required to tell the compiler if you want `param` to be treated as an lvalue or rvalue – Jonathan Wakely Dec 10 '14 at 20:16
  • And the way you do that is `std::forward`. It's a convention for telling the compiler what you want to happen. You can keep asking "why do I have to do that?" and the answer will be the same: **because if you don't use `forward`** (or an equivalent cast) **then `param` will never be treated as an rvalue, because that's how C++ works**. – Jonathan Wakely Dec 10 '14 at 20:18
  • std::forward specially design to force you to specify template parameter. But after you do instantiate std::forward compiler knows which of the two std::forward overloads to use (the one that takes T&& or the one that takes T&). So if he can figure out implicitly which overloaded std::forward function to use why it can not do the same for c'tor from my example ?! – Alejandro Freeman Dec 10 '14 at 21:05
  • It almost always uses the overload taking `T&`, e.g. `std::forward(param)` always calls that one **because `param` is an lvalue**. The other overload exists for cases such as `std::forward(function_returning_rvalue())` but that is rarely needed. Maybe you think the compiler decides whether to call the `T&` one or the `T&&` one based on whether `param` was originally an lvalue or rvalue, but that is wrong. The compiler is **not** figuring out which one to call for `std::forward(param)`, that **always** calls the same one. – Jonathan Wakely Dec 10 '14 at 21:22
  • To put it another way, the compiler can figure out which `forward` to call based on whether the input is an lvalue or rvalue, but that is unrelated to whether you want the desired output to be an lvalue or rvalue. The compiler knows the type of the input, but you need to explicitly tell it the type of the output, **because they are not the same!** e.g. `std::forward(param)` has an lvalue input and an rvalue output. `std::forward(param)` has an lvalue input and lvalue output. – Jonathan Wakely Dec 10 '14 at 21:26
  • foo("test") -> std::forward that takes string&& is invoked and return value collapses to ravalue reference and std::string move c'tor is called. string tmp = "test"; foo(tmp) -> std::forward that takes string& is invoked return value collapses to lvalue reference and string's copy c'tore is called. How compiler knew which std::forward overload to call ? – Alejandro Freeman Dec 10 '14 at 21:45
  • No, when you call foo("test") the `std::forward` that takes string& is invoked, because **param is an lvalue**. I have said this lots of times, I'm not going to waste any more time because you clearly aren't listening. **When you call `std::forward(param)` the input is an lvalue but the output is an rvalue** – Jonathan Wakely Dec 11 '14 at 01:11
  • I do understand what you say. But regarding my previous comment I've tested it with debugger using Eclipse CDT MinGW 4.9.1 and those were the results I've posted I did not made that up ! – Alejandro Freeman Dec 11 '14 at 07:38
  • It's because you're using forward not forward (as would be conventional), so you force the construction of a temporary `string` from the `const char*`. You wouldn't usually use `forward` to alter the type, only to cast to lvalue/rvalue. i.e. you're using it strangely – Jonathan Wakely Dec 11 '14 at 11:06