skip to content
Mehdi Mehdikhani
Table of Contents

std::move and std::forward are closely related but they solve a different problem: perfect forwarding.

What std::forward Actually Does

Like std::move, std::forward doesn’t actually forward anything. It’s a conditional cast that preserves the “value category” of its argument. If you pass an rvalue, it stays an rvalue. If you pass an lvalue, it stays an lvalue.

template<typename T>
void wrapper(T&& arg) {
target_function(std::forward<T>(arg)); // Preserves value category
}

Without std::forward, everything becomes an lvalue inside the template function, even if you originally passed an rvalue.

The Problem It Solves

Before perfect forwarding, writing generic wrapper functions was annoying. You had to provide overloads for every combination of lvalue and rvalue references:

// lots of overloads
void wrapper(const std::string& s) { target_function(s); }
void wrapper(std::string&& s) { target_function(std::move(s)); }

With perfect forwarding, one template handles all cases:

// The new way - one template for everything
template<typename T>
void wrapper(T&& arg) {
target_function(std::forward<T>(arg));
}

When I Actually Use std::forward

Most of the time, I use std::forward in these situatoins:

  1. Factory functions: When creating objects and passing arguments:
template<typename T, typename... Args>
std::unique_ptr<T> make_object(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
auto obj1 = make_object<MyClass>(some_string); // lvalue
auto obj2 = make_object<MyClass>(std::move(temp)); // rvalue
  1. Wrapper functions: When I need to forward arguments to another function:
template<typename F, typename... Args>
auto time_function(F&& func, Args&&... args) {
auto start = std::chrono::steady_clock::now();
auto result = func(std::forward<Args>(args)...);
auto end = std::chrono::steady_clock::now();
return result;
}
  1. Constructors that forward to other constructors:
class MyClass {
std::string name_;
std::vector<int> data_;
public:
template<typename... Args>
MyClass(std::string name, Args&&... args)
: name_(std::move(name)),
data_(std::forward<Args>(args)...) {}
};

The Key Difference

The main difference between std::move and std::forward:

  • std::move always casts to rvalue reference
  • std::forward conditionally casts based on the template parameter
std::string text = "hello";
std::move(text); // Always rvalue reference
std::forward<T>(text); // Depends on what T is

Universal References

std::forward only makes sense with universal references (the T&& in templates). Regular rvalue references don’t need forwarding:

// Universal reference - use std::forward
template<typename T>
void func(T&& arg) { other_func(std::forward<T>(arg)); }
// Regular rvalue reference - use std::move
void func(std::string&& arg) { other_func(std::move(arg)); }

The Rule I Follow

I use std::forward when I have a template function that gets universal reference and needs to pass them to another function. It’s all about preserving the original intent - if someone passed an rvalue, they probably want move semantics.

The pattern is almost always the same: std::forward<T>(arg) where T is the template parameter and arg is the universal reference parameter.

Perfect forwarding is what makes functions like std::make_unique and std::make_shared work efficiently - they can construct objects with any combination of arguments without unnecessary copies.