34

In VS2010 std::forward is defined as such:

template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{   // forward _Arg, given explicitly specified type parameter
    return ((_Ty&&)_Arg);
}

identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?

David
  • 27,652
  • 18
  • 89
  • 138
  • 4
    Note that currently `std::forward` is declared as `template T&& forward(typename std::remove_reference::type& t); template T&& forward(typename std::remove_reference::type&& t);` (and both return `static_cast(t)`). MSVC is following a previous version of `forward`. – Luc Danton Oct 15 '11 at 19:13
  • 3
    Interesting. How is that different from `template T&& forward(T&& t){ return static_cast(t); }`? I see the purpose of `std::remove_reference` in `std::move`, but not here. – David Oct 15 '11 at 19:32
  • The two overload version works with rvalues and doesn't require `std:identity` which was in fact dropper altogether from the Standard. – Luc Danton Oct 15 '11 at 19:37

3 Answers3

32

If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.

If std::forward used template argument deduction:

Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.

Example:

template<typename T>
T&& forward_with_deduction(T&& obj)
{
    return static_cast<T&&>(obj);
}

void test(int&){}
void test(const int&){}
void test(int&&){}

template<typename T>
void perfect_forwarder(T&& obj)
{
    test(forward_with_deduction(obj));
}

int main()
{
    int x;
    const int& y(x);
    int&& z = std::move(x);

    test(forward_with_deduction(7));    //  7 is an int&&, correctly calls test(int&&)
    test(forward_with_deduction(z));    //  z is treated as an int&, calls test(int&)

    //  All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
    //  an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int& 
    //  or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what 
    //  we want in the bottom two cases.
    perfect_forwarder(x);           
    perfect_forwarder(y);           
    perfect_forwarder(std::move(x));
    perfect_forwarder(std::move(y));
}
David
  • 27,652
  • 18
  • 89
  • 138
  • Great that you got your head wrapped around how and why `std::forward` works like it does. :) +1 – Xeo Jan 15 '12 at 03:37
  • 4
    Do note that just removing `remove_reference` will keep everything working - as long as `forward` accepts by `&` and you keep the explicit template specification when calling it. The explicit template specification is required to make this work, so `remove_reference` makes sure we don't forget it. Your example with the forwarding reference (`&&`) is a red herring, IMHO – Eli Bendersky Nov 04 '14 at 14:02
20

Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in

template<typename Arg>
void generic_program(Arg&& arg)
{
    std::forward(arg);
}

std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.

So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.


I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.

template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }

This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:

template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }

then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.

In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 2
    So what does `std::forward(t)` do differently from `std::forward(t)`? – Daniel Oct 15 '11 at 19:12
  • 4
    I don't understand why it would be a no-op if it deduced the template argument type instead of me explicitly giving it. Could you elaborate/clarify? – David Oct 15 '11 at 19:14
  • "any possible use of std::forward(t) are trivially replaced by t" I don't think that's true; the former is an r-value, the latter is not. – avakar Oct 15 '11 at 19:29
  • 2
    @avakar No. Since we're passing a variable to `std::forward`, `T` would be deduced to an lvalue reference type and reference collapsing rules would ensure that the return type is that same lvalue reference type, thus the call would be an lvalue. Similarly, `std::forward(0)` would be an xvalue. Hence, a deducing `std::forward` would perfectly forward its argument and be a no-op. – Luc Danton Oct 15 '11 at 19:40
  • I noticed an overuse of `template`, I changed my latest example to `template` to distinguish from the template parameter of `std::forward`. I think it helps with understanding that when perfect-forwarding is involved, a deduced parameter can be a reference type. – Luc Danton Oct 15 '11 at 19:51
  • I appreciate the effort but I can't make sense of half of what you wrote. Alot of it appears to be attempting to explain things I already knew. I think you're trying to say template argument deduction would result in T equaling the wrong type in certain cases - though I can't see why. – David Oct 15 '11 at 20:19
  • @Dave I'm also having trouble explaining as I don't know my target audience. Since rvalue references, reference collapsing rules and template parameter deduction were all special purposed to make perfect forwarding possible it's a bit hard to explain any one aspect of it without getting tangled in the whole bit. Do you understand what is the value category of `std::forward(U)` and can you compare it to the value category of a hypothetical `std::forward(u);`? – Luc Danton Oct 15 '11 at 20:24
  • Regarding your first post: _std::forward(t) is semantically equivalent to t_ - that seems wrong. Since `t` is _named_ shouldn't that make it impossible to use as an rvalue reference even when T = int&& (unless you explicitly cast it to a T&& (which would be the point of std::forward, right?) – David Oct 15 '11 at 20:25
  • 2
    @Dave Right. But `t` isn't used as an rvalue reference, I'm not sure why you mention that. Do you mean binding to the parameter of `std::forward`? Do you understand that in `template void foo(T&&);` then `T&&` may or may not be an rvalue reference? – Luc Danton Oct 15 '11 at 20:29
  • @Luc : It seems clear they don't understand reference collapsing rules. – ildjarn Oct 15 '11 at 21:55
  • Sort of a nit, I guess, but: "Similarly, std::forward(0) would be an xvalue. Hence, a deducing std::forward would perfectly forward its argument and be a no-op." Aren't you contradicting yourself here? 0 isn't an xvalue, std::forward(0) is an xvalue, so a deducing std::forward wouldn't be a no-op, it would turn prvalues into xvalues. The difference can be observed when declaring a variable of type decltype(forward(0)) vs. one declared decltype(0). It's still completely useless, though. –  Apr 27 '14 at 09:19
  • 1
    @hvd Function templates cannot tell prvalues apart from xvalues. The final, bound argument is a ‘value’ of type `int&&`, and it is the return ‘value’ as well. Hence, a no-op. – Luc Danton Apr 27 '14 at 11:08
  • Indeed, you really need decltype to tell them apart, function templates wouldn't be enough for that. –  Apr 27 '14 at 17:49
  • @LucDanton, I bumped into this answer while studying _Effective Modern C++_ by Scott Mayers, since I haven't mastered perfect `forward`ing completely (I'm a bit more comfortable with `move`, though!). Anyway, Mayers writes an implementation of `std:forward` to make the topic less elusive, but, unfortunately, he doesn't make explicit difference between the template parameter relative to the template implementation of `forward` (he uses `T`) and the that of the no-op function template (he uses `T`, you used `U`). – Enlico Dec 18 '18 at 17:50
  • (@LucDanton, comment continued here) Maybe inserting that chunk could help readers (_e.g._ me) that consider themeselves as being exactly on the line between _having a vague idea_ and _having catched the secret_. Maybe you could take it as an occasione to give the definitive revision to your already remarkable answer. – Enlico Dec 18 '18 at 17:51
  • @EnricoMariaDeAngelis I deliberately avoid revealing or talking too much about an implementation of `std::forward` because I consider it a hallmark of good programming to code against the interface, not the implementation. That is of course a bit self-defeating here, due to the importance of this `T` parameter (which is part of the interface, but serves a very messy, gritty purpose nonetheless). That being said, your comments are on point (and appreciated) and I can at the very least improve on these awful single-letter template parameter names. – Luc Danton Dec 19 '18 at 03:23
2

Short answer:

Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.

Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).

And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.

Details:

Example:

template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }

Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).

Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:

  • If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
  • If an rvalue of type _T && is passed to someFuncT will be deduced as _T &&

Now, you can replace T with the true type in above code:

When lvalue obj is passed:

auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }

And after applying reference collapse rule(, pls read Meyers' artical), we get:

auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }

When rvalue obj is passed:

auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }

And after applying reference collapse rule(, pls read Meyers' artical), we get:

auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }

Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.

But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.

If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).

Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.

Archer
  • 592
  • 1
  • 4
  • 11