How to Typically/Always Use Std::Forward Instead of Std::Move

Can I typically/always use std::forward instead of std::move?

The two are very different and complementary tools.

  • std::move deduces the argument and unconditionally creates an rvalue expression. This makes sense to apply to an actual object or variable.

  • std::forward takes a mandatory template argument (you must specify this!) and magically creates an lvalue or an rvalue expression depending on what the type was (by virtue of adding && and the collapsing rules). This only makes sense to apply to a deduced, templated function argument.

Maybe the following examples illustrate this a bit better:

#include <utility>
#include <memory>
#include <vector>
#include "foo.hpp"

std::vector<std::unique_ptr<Foo>> v;

template <typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // #1
}

int main()
{
{
std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3)));
v.push_back(std::move(p)); // #2
}

{
v.push_back(make_unique<Foo>('b', false, Bar(5,6,7))); // #3
}

{
Bar b(4,5,6);
char c = 'x';
v.push_back(make_unique<Foo>(c, b.ready(), b)); // #4
}
}

In situation #2, we have an existing, concrete object p, and we want to move from it, unconditionally. Only std::move makes sense. There's nothing to "forward" here. We have a named variable and we want to move from it.

On the other hand, situation #1 accepts a list of any sort of arguments, and each argument needs to be forwarded as the same value category as it was in the original call. For example, in #3 the arguments are temporary expressions, and thus they will be forwarded as rvalues. But we could also have mixed in named objects in the constructor call, as in situation #4, and then we need forwarding as lvalues.

Is there any benefit in using std::forward instead of std::move to initialize an object?

Using a forwarding reference and std::forward you can eliminate the creation of an extra object.

If you don't use a forwarding reference, you have three objects involved:

  1. Caller's original object
  2. Function parameter f, constructed using the copy or move constructor as appropriate
  3. Bind object's internal object, constructed by move constructor

If you use a forwarding reference with std::forward, you eliminate the second one. There will only be two objects created:

  1. Caller's original object
  2. Bind object's internal object, constructed using the copy or move constructor as appropriate

While move-constructing an object may be cheaper than copy-constructing (depending on the type), it still contributes some overhead that perfect-forwarding can avoid.

Why not always use std::forward?

Eh. Debatable. In your particular example it is equivalent. But I wouldn't make it a habit. One reason is because you want a semantic distinction between forwarding and moving. Another reason is because to have a consistent API you'd have to have MOV in addition to FWD, and that really looks bad and doesn't do anything. More importantly, though, is that your code can fail unexpectedly.

Consider the following code:

#include <iostream>

using namespace std;

#define FWD(arg) std::forward<decltype(arg)>(arg)

struct S {
S() { cout << "S()\n"; }
S(const S&) { cout << "S(const S&)\n"; }
S(S&&) { cout << "S(S&&)\n"; }
};

void some_func(S) {}

void f(S&& s)
{
some_func(FWD(s));
}

int main()
{
f(S{});
}

This prints out

S()

S(S&&)

However, if I just change the FWD line to have another (seemingly optional) pair of parentheses, like this:

void f(S&& s)
{
some_func(FWD((s)));
}

Now we get

S()

S(const S&)

And this is because now we're using decltype on an expression, which evaluates to an lvalue reference.

What are the scenarios where assignment from std::forward is preferable over assignment from std::move ? why?

The fact that there's assignment is irrelevant. What distinguishes forward from move is what kind of argument they take when used inside your function:

  • The argument of forward is a forwarding reference parameter of your function.
  • The argument of move is an rvalue reference parameter of your function (or more generally, anything you know to have no further aliases).

If you exchange with an lvalue, you don't want that to be moved-from!

For example, consider:

std::vector<int> a, b;

f(std::exchange(a, b));

This effectively calls f(a) but also performs a = b;. It does not perform a = std::move(b); b is still usable in its original form afterwards!

See also this answer of mine, as well as many other related answers like What is std::move(), and when should it be used?, What's the difference between std::move and std::forward, and links therein.

Why use std::forwardT instead of static_castT&&

forward expresses the intent and it may be safer to use than static_cast: static_cast considers conversion but some dangerous and supposedly non-intentional conversions are detected with forward:

struct A{
A(int);
};

template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
return static_cast<Arg1&&>(a2); // typing error: a1=>a2
}

template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
return forward<Arg1>(a2); // typing error: a1=>a2
}

void test(const A a,int i){
const A& x = f(a,i);//dangling reference
const A& y = g(a,i);//compilation error
}

Example of error message: compiler explorer link


How applies this justification: Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.

Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?

When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:

  template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}

where foo_1,foo_2,foo_3 are templates, the instantiation of foo will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.

So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.

So, in my opinion using static_cast in place forward to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.

Why is std::forward useless in this context

It's not wrong to use std::forward here (per se), but it is inappropriate and thus misleading (in that sense, you could say that it is actually wrong).

The way you spell "cast something to an rvalue reference to its type" is std::move. That is the only thing that std::move does—it's a static_cast<T&&> where you don't have to spell out the T.

std::forward is a different beast, intended for use with perfect forwarding. Perfect forwarding only occurs in templates, where forwarding references are possible. A forwarding reference is a reference which, thanks to a special rule in the language, can deduce to either an lvalue reference or an rvalue reference. There, you need std::forward<T> to re-instate the appropriate value category (lvalue or rvalue).

Toy example:

void print(int &)
{
std::cout << "Lvalue\n";
}

void print(int &&)
{
std::cout << "Rvalue\n";
}

template <class T>
void perfectForwarder(T &&v)
{
print(std::forward<T>(v));
}

int main()
{
int i;
perfectForwarder(i);
perfectForwarder(std::move(i));
perfectForwarder(42);
}

The output will be:

Lvalue

Rvalue

Rvalue

[Live]

Why is std::forward necessary with forwarding references

You really don't want your parameters to be automatically moved to the functions called. Consider this function:

template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}

Now you really don't want an automatic move to bar and an empty parameter to baz.

The current rules of requiring you to specify if and when to move or forward a parameter are not accidental.

Move and Forward cases use

is there a difference between using std::move and std::forward? Why should I use one instead of the other?

Yes, std::move returns an rvalue reference of its parameter, while std::forward just forwards the parameter preserving its value category.

Use move when you clearly want to convert something to an rvalue. Use forward when you don't know what you've (may be an lvalue or an rvalue) and want to perfectly forward it (preserving its l or r valueness) to something. Can I typically/always use std::forward instead of std::move? is a question you might be interested in here.

In the below snippet, bar would get exactly what the caller of foo had passed, including its value category preserved:

template <class T>
void foo(T&& t) {
bar(std::forward<T>(t));
}

Don't let T&& fool you here - t is not an rvalue reference. When it appears in a type-deducing context, T&& acquires a special meaning. When foo is instantiated, T depends on whether the argument passed is an lvalue or an rvalue. If it's an lvalue of type U, T is deduced to U&. If it's an rvalue, T is deduced to U. See this excellent article for details. You need to understand about value categories and reference collapsing to understand things better in this front.

move vs forward on universal references

Universal references mean that they can bind with apparently anything, both rvalues and lvalues. move & forward donot MOVE ANYTHING LITERALLY and rather perform casts ie they cast the argument to an rvalue. The difference is that move casts unconditionally whereas forward will cast conditionally: it casts to an rvalue only if its argument was initialized with an rvalue. So move always casts whereas forward casts sometimes.
Hence the forward in the above case doesn't empty the string as an lvalue (ie str) was passed so it FORWARDED str as an lvalue.

When to use std::forward to forward arguments?

Use it like your first example:

template <typename T> void f(T && x)
{
g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
g(std::forward<Args>(args)...);
}

That's because of the reference collapsing rules: If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, so you always end up with the correct type inside the function body. Finally, you need forward to turn the lvalue-turned x (because it has a name now!) back into an rvalue reference if it was one initially.

You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially moving the argument all the way through to the final caller, and once it's moved it's gone, so you cannot then use it again (in the way you probably meant to).



Related Topics



Leave a reply



Submit