Templates[temp]

std::forward in C++: perfect forwarding explained

Section
Templates [temp]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer8 clauses
  1. 1Why is std::forward needed?
  2. 2What is a forwarding reference?
  3. 3std::forward vs std::move: what is the difference?
  4. 4How do you forward several arguments?
  5. 5How do you forward in a generic lambda?
  6. 6Common std::forward mistakes
  7. 6.1Forwarding the same argument twice
  8. 6.2A T&& that is not a forwarding reference
  9. 6.3std::move on a forwarding reference
  10. 7Do you still need std::forward in C++23?
  11. 8When to write std::forward, std::move or neither

Abstract

std::forward<T>(arg) passes a function template's argument on as what it was when it arrived: an lvalue stays an lvalue, an rvalue becomes an rvalue again. Use it on a forwarding reference (a T&& parameter with T deduced, or auto&&), once, where you pass the argument on, because inside the function the named parameter is always an lvalue. Like std::move it is only a cast; any move is made by the function that receives the rvalue and takes it.

A bold frame with two lines running through it from left to right. The upper line is solid; the lower one, in blue, enters dashed, runs solid inside the frame and leaves dashed.
Figure 1 — What goes in as an lvalue or an rvalue comes out as the same.

std::forward<T>(arg) passes a function template’s argument on to another function as what it was when it arrived: an lvalue (a named variable) stays an lvalue, and an rvalue (such as a temporary) becomes an rvalue again. Use it on a T&& parameter with T deduced, a forwarding reference; that is perfect forwarding. It is in <utility>.

A std::forward example, as a whole program: process is overloaded for lvalues and rvalues, and two wrappers pass their argument on, one without std::forward and one with it.

[Example 1:
#include <iostream>
#include <string>
#include <utility>

void process(const std::string&) {
    std::cout << "lvalue: copy\n";
}

void process(std::string&&) {
    std::cout << "rvalue: move\n";
}

template <typename T>
void without(T&& arg) {
    process(arg);                   // arg has a name: always an lvalue
}

template <typename T>
void with(T&& arg) {
    process(std::forward<T>(arg));  // what the caller passed
}

int main() {
    std::string name = "Ada";

    std::cout << "without std::forward\n";
    without(name);                  // lvalue: copy
    without(std::string("Bob"));    // lvalue: copy

    std::cout << "with std::forward\n";
    with(name);                     // lvalue: copy
    with(std::string("Bob"));       // rvalue: move
}

— end example]

It printed:

[Example 2:
without std::forward
lvalue: copy
lvalue: copy
with std::forward
lvalue: copy
rvalue: move

— end example]

The samples on this page are C++17, built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same. std::forward itself dates from C++11; Since When lists what each standard added.

Why is std::forward needed?

Because a parameter has a name, and a named variable is an lvalue whatever the caller passed. In without, arg was bound to the temporary "Bob" on the second call, yet process(arg) chose the const std::string& overload both times. std::forward<T>(arg) turns arg back into an rvalue when the caller passed one, so in with the temporary reached process(std::string&&).

Nothing in this program is copied or moved: both overloads take a reference and only print. Their texts name what each is entitled to do with the string: the const std::string& overload can only copy it, and the std::string&& overload may move from it. The two paths through the wrapper, lvalues solid and rvalues dashed:

Two rows. Without std::forward, solid lvalue and dashed rvalue arrows pass through a box, T&& arg, and both reach process(const std::string&), marked can only copy; the other overload is grey. With std::forward, the rvalue leaves dashed for process(std::string&&), marked may move from it.
Figure 2 — Inside the wrapper the parameter is an lvalue; std::forward turns it back into an rvalue only when the caller passed one.

What is a forwarding reference?

A forwarding reference is a parameter declared T&& where T is a template parameter of that function, deduced from the argument; auto&& is one too. It binds to lvalues and rvalues alike and records which one it got in T (temp.deduct.call). Older writing calls it a universal reference. With a std::string argument:

Argument T is The parameter T&& is
An lvalue std::string& std::string&
A const lvalue const std::string& const std::string&
An rvalue std::string std::string&&

The first two rows rest on reference collapsing (dcl.ref): a reference to a reference is & unless both are &&, so & &, & && and && & give &, and only && && gives &&. The const travels in T, so a const argument stays const. std::forward<T> reads that T back: the standard defines its result as static_cast<T&&>(t) (forward), an lvalue when T is std::string& and an rvalue when it is std::string.

std::forward vs std::move: what is the difference?

std::move(x) always makes an rvalue; std::forward<T>(x) makes one only when the caller passed an rvalue.

Question std::move(x) std::forward<T>(x)
What it returns an rvalue, always an rvalue if the caller passed one, else an lvalue
Use it on an object you own and are done with a forwarding reference, once, where you pass it on
The type deduced from x written out: <T>

Neither moves anything: both are casts, and the move happens in the function that receives the rvalue and takes it, as move semantics and std::move explains. The <T> is not optional: std::forward(arg) did not compile on any of the four, because its parameter, remove_reference_t<T>&, gives the compiler nothing to deduce T from.

How do you forward several arguments?

Take a parameter pack of forwarding references, Args&&... args, and expand std::forward<Args>(args)...: one std::forward per argument, each with its own type. Here make builds a Widget from a name and a size, through one constructor that copies the name or one that moves it:

[Example 3:
struct Widget {
    std::string name;
    int size;

    Widget(const std::string& n, int s) : name(n), size(s) {
        std::cout << "Widget: copied the name\n";
    }
    Widget(std::string&& n, int s) : name(std::move(n)), size(s) {
        std::cout << "Widget: moved the name\n";
    }
};

template <typename T, typename... Args>
T make(Args&&... args) {
    return T(std::forward<Args>(args)...);
}

int main() {
    std::string label = "knob";

    Widget a = make<Widget>(label, 3);                // copied
    Widget b = make<Widget>(std::string("dial"), 5);  // moved

    std::cout << a.name << ' ' << a.size << ", "
              << b.name << ' ' << b.size << ", label: " << label
              << '\n';
}

— end example]

It printed:

[Example 4:
Widget: copied the name
Widget: moved the name
knob 3, dial 5, label: knob

— end example]

The named label went to the copying constructor and still held knob afterwards; the temporary "dial" went to the moving one, and the int travelled beside the name both times. std::make_unique and emplace_back pass constructor arguments through the same way. Parameter packs and fold expressions are in the templates guide.

How do you forward in a generic lambda?

A generic lambda’s auto&& parameter is a forwarding reference too, but there is no T to name, so write std::forward<decltype(x)>(x). decltype(x) is std::string& for an lvalue and std::string&& for an rvalue (a deduced T would be plain std::string); std::forward<std::string&&> returns std::string&& &&, which collapses to std::string&&, so either one is read correctly:

[Example 5:
auto call = [](auto&& x) {
    process(std::forward<decltype(x)>(x));
};

std::string name = "Ada";
call(name);                  // lvalue: copy
call(std::string("Bob"));    // rvalue: move

— end example]

It printed lvalue: copy, then rvalue: move, as with did. Generic lambdas and function objects covers the pack form, auto&&... args.

Common std::forward mistakes

Two of these three compile without a warning at /W4 or -Wall -Wextra.

Forwarding the same argument twice

Forward an argument once, at its last use: the first std::forward may hand it to a function that moves from it, and the second call gets what is left. Here log_name and save_name both take a std::string by value:

[Example 6:
template <typename T>
void record(T&& name) {
    log_name(std::forward<T>(name));   // may move out of name
    save_name(std::forward<T>(name));  // may see a moved-from string
}

— end example]

Given a temporary, save_name got 0 characters on all three standard libraries; given an lvalue, both functions got a copy. The standard leaves a moved-from string unspecified, so it may be empty, and was here. clang-tidy’s bugprone-use-after-move check reports the second call: 'name' used after it was forwarded.

A T&& that is not a forwarding reference

T&& is a forwarding reference only when T is deduced at that call. In Box<T>, a class template, the member put(T&& x) uses the class’s T, so Box<std::string>::put takes rvalues only, and an lvalue std::string s does not bind:

[Example 7:
b.put(s);                       // error: s is an lvalue

— end example]

Clang’s error:

[Example 8:
error: rvalue reference to type 'basic_string<...>' cannot bind to lvalue of type 'basic_string<...>'

— end example]

const T&& is not a forwarding reference either. A member that must take lvalues too needs a template parameter of its own, template <typename U> void put(U&& x), and passes std::forward<U>(x) on.

std::move on a forwarding reference

std::move(arg) in a wrapper passes even the caller’s named variable on as an rvalue, so the function it reaches may move from it. When that function took its parameter by value, as keep(std::string mine) does, the caller’s name was empty afterwards on all four builds; with process, which only takes a reference, it still held "Ada".

Do you still need std::forward in C++23?

Yes: for a parameter of a function template, std::forward is still the tool. C++23 adds std::forward_like, which forwards a member the way the object holding it was passed, most often with an explicit object parameter (this auto&& self, “deducing this”, also C++23); all four compilers built the pair as C++23.

When to write std::forward, std::move or neither

Which one depends on what you hold:

You hold Write
A T&& parameter, T deduced, passed on std::forward<T>(arg), once, at its last use
A pack Args&&... args std::forward<Args>(args)...
A generic lambda’s auto&& x std::forward<decltype(x)>(x)
A member, through this auto&& self std::forward_like<decltype(self)>(self.name), C++23
An object you own and are done with std::move(x)
An argument you only read neither: take it by const& or by value

Functions that use their arguments rather than pass them on need no forwarding: whether to pass by value or by const reference settles theirs. The Templates section has the other answers on templates.

Questions

What does std::forward do?

std::forward<T>(arg) passes a function template's argument on to another function as what it was when it arrived: an lvalue stays an lvalue and an rvalue becomes an rvalue again. It is used on a forwarding reference, a T&& parameter whose T the call deduced.

What is perfect forwarding?

Passing arguments through a function template to another function with their value category and constness unchanged: an lvalue arrives as an lvalue, an rvalue as an rvalue, a const argument as const. In C++ it is written as a forwarding reference parameter (T&& with T deduced, or auto&&) passed on with std::forward.

When should I use std::forward instead of std::move?

On a forwarding reference, once, at the point where you pass the argument on. Use std::move on an object you own and are done with. std::move on a forwarding reference passes the caller’s named variable on as an rvalue too, so a function that takes it by value can leave the caller’s variable empty.

Which header is std::forward in?

The <utility> header. std::forward has been in the standard library since C++11, and constexpr since C++14.

What is a forwarding reference?

A function parameter declared T&&, where T is a template parameter of that function deduced from the argument, or a parameter declared auto&&. It binds to lvalues and rvalues alike and records which one it got in T. T&& in a member of a class template, whose T belongs to the class, and const T&& are rvalue references instead. Older writing calls it a universal reference.

How is std::forward implemented?

Its implementation is a cast and nothing more. The standard gives it two overloads, template<class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept and the same taking remove_reference_t<T>&&; both return static_cast<T&&>(t), and the second refuses to compile when T is an lvalue reference. It moves nothing: any move is made by the function that receives the rvalue and takes it, such as a move constructor.

Sources

  1. std::forwardcppreference.com
  2. Reference declarationcppreference.com
  3. std::forward_likecppreference.com
  4. [forward] Forward/move helpersC++ standard working draft
  5. [temp.deduct.call] Deducing template arguments from a function callC++ standard working draft
  6. [dcl.ref] ReferencesC++ standard working draft
  7. <utility> functionsMicrosoft Learn

Read next

  1. C++ templates: how to write function and class templatesTemplates · Miriam Castellanos · written for C++17
  2. C++ generic lambdas, template lambdas and function objectsTemplates · Miriam Castellanos · written for C++20
  3. std::function in C++: what it costs, what to use insteadTemplates · Miriam Castellanos · written for C++17