10

As far as I know, in C++11, universal reference should always be used with std::forward, but I am not sure of what kind of problem can occur if std::forward is not used.

template <T>
void f(T&& x);
{
    // What if x is used without std::forward<T>(x) ?
}

Could you provide some illustrations of problems that could occur in this situation ?

Vincent
  • 57,703
  • 61
  • 205
  • 388

4 Answers4

16

There is no such rule to always use std::forward with universal references. On the contrary, it can be dangerous to use std::forward all over the place in functions with universal references. Take a look at the following example:

template <typename T>
auto make_pair(T&& t)
{
    return std::make_tuple(std::forward<T>(t), std::forward<T>(t)); // BAD
}

If you call this function with make_pair(std::string{"foobar"}), the result is counter-intuitive, because you move from the same object twice.


Update: Here is another example to show, that it really makes sense to use universal references without perfect forwarding:

template <typename Range, typename Action>
void foreach(Range&& range, Action&& action)
{
    using std::begin;
    using std::end;
    for (auto p = begin(range), q = end(range); p != q; ++p) {
        action(*p);
    }
}
  • It's good that range is a universal reference, so that the caller can use foreach with a temporary container and an action, that's calls a non-const member function on the elements.
  • It's good that action is a universal reference, so that the caller can pass a mutable lambda expression as action.
  • And it would be wrong to use std::forward for range or for action.
nosid
  • 48,932
  • 13
  • 112
  • 139
  • 1
    Very good point but it doesn't clearly answer the question "what kind of problem can occur if std::forward is not used?" – Nicolas Apr 25 '14 at 15:49
  • +1, but why would it be wrong to use `std::forward` for `action`? I just read this: [Why use a perfectly forwarded value?](http://stackoverflow.com/q/24779910/873025), and it seems you may throw away an optimization opportunity if not preserving the r-valueness. – Felix Glas Aug 06 '14 at 20:13
  • @Snps: In contrast to `std::apply`, the above code contains a loop. Depending on the type of `Action`, `std::forward(action)` can be the same as `std::move(action)`. That means with `std::forward` you would potentially change the function object `action` in the first iteration and causing strange behaviour for all remaining iterations. – nosid Aug 07 '14 at 16:54
  • @nosid Duh, the loop (how could I miss that). Makes sense now, thanks for clearing that up! – Felix Glas Aug 07 '14 at 17:16
  • The first example is pretty bad. If the purpose is to `make_tuple(t, t)`, then `T&&` probably should not be used in the first place—`const T&` would be better. – Yongwei Wu May 04 '22 at 03:19
10

Let's say f is called like this:

f(someType{});

If the body of f performs some kind of operation on x

foo(x);

and there are two overloads for foo

void foo(someType const&);

void foo(someType&&);

without using std::forward, as x is an lvalue the following overload is called

void foo(someType const&);

which might cost you a potential optimization.

Using

foo(std::forward<T>(x));

makes sure the correct overload of foo is selected.

user657267
  • 20,568
  • 5
  • 58
  • 77
6

Things with a name are lvalues. That means that in the function body, t is an lvalue. It doesn't matter if the universal reference ends up as an lvalue reference or as an rvalue reference. If it's named, it's an lvalue.

If you thus pass that argument along directly, you will be passing an lvalue.

In a situation where you want to just pass along an opaque argument to another function, you want to pass it exactly as-is. If it was an lvalue, you want to pass it as an lvalue; if it was an rvalue you want to pass an rvalue. As explained, passing it directly passes it as an lvalue always. forward does the magic needed to pass it as the right kind of value,

Code that always passes an lvalue will likely suffer from a performance perspective but I would say that you can ignore that when thinking about this particular issue. There is a more pressing concern for not always passing an lvalue: while copying some types may be expensive, some other types cannot be copied at all, like std::unique_ptr. For those types preserving rvalueness when forwarding is not a matter of performance but of getting your code to even compile.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
0

You should not forward a variable more than once.

Mikhail Semenov
  • 953
  • 8
  • 8