What is the difference between std::forward<T>() and std::move when perfectly forwarding rvalue references?

Viewed 191

I was wondering what the difference is (if there is one) between using std::move() and using std::forward() in the below code sample:

struct Foo {};

class Bar {

private:
  std::vector<Foo> storage;

public:

  template<typename T>
  void add(T&& t) {

     constexpr auto isFoo = std::is_same_v<std::decay_t<T>,Foo>;
     
     static_assert(isFoo,"Attempting to push non-Foo type to vector");
     
     if constexpr(isFoo){
         
        storage.push_back(std::forward<T>(t));
     }
  }
};


class BarProxy {

private:
  Bar &bar;

public:
  BarProxy(Bar &bar) : bar{bar} {}

  void add(const Foo &foo) {

     bar.add(foo);
  }

  void add(Foo &&foo) {
    
     bar.add(std::move(foo));
     //bar.add(std::forward<Foo>(foo)); is there a difference between using this and std::move(foo)?
  }
};

int main() {

    Bar bar = Bar{};
    BarProxy proxy = BarProxy(bar);

    proxy.add(Foo{});

    return 0;
}

Is there a difference between using std::forward and std::move in add(Foo &&foo) method?

1 Answers

This is the TLDR of what std::forward is for:

template <class T>
void f1(T&& foo) {
   ...
}

template <class T>
void f2(T&& bar) {
   ...
   f1(bar);
   ...
}

...

f2(/*see explanation*/);

You could say that f2 forwards its argument to f1. So depending on what you replace the comment with, and depending on what exactly f1 needs to do with its data, you might end up in a situation where you'd need two overrides like

template <class T>
void f1(const T& foo) {...}

template <class T>
void f1(T& foo) {...}

This is an... okay solution if you only have one parameter. But if f1 takes two parameters, then you need four overrides to handle all combinations, if it takes three parameters, you need nine overrides, etc. You can see that this quickly becomes untenable when you add arguments. This is where forward comes in. Here's the original example with forward:

template <class T>
void f1(T&& foo) {
   ...
}

template <class T>
void f2(T&& bar) {
   ...
   f1(std::forward(bar));
   ...
}

Now, you don't have the problem I described earlier. Here's a more in-depth explanation that goes into how and why this works, along with an explainer of C++11's concept of "universal references"

Related