← SEARCHABLE ARCHIVE
C / C++ · 2026-08-13

C++ from Basics to Mastery — Part 19

Preserve value categories with perfect forwarding

INTERACTIVE READING MODE →

The articles on this blog are written for teaching purposes. The information may contain mistakes, so verify it against your target compiler and standard library.

Even after learning move semantics, a generic wrapper can accidentally turn every argument into an lvalue. Perfect forwarding preserves whether the caller supplied an lvalue or an rvalue when the wrapper calls the next function.

A named expression is an lvalue

void Store(const Texture& texture); // read
void Store(Texture&& texture);      // may take ownership

Texture loaded = LoadTexture();
Store(loaded);              // lvalue overload
Store(std::move(loaded));   // rvalue overload
Store(LoadTexture());       // rvalue overload

An lvalue identifies a persistent object. An rvalue may denote a temporary or an object explicitly offered for moving. Even a variable whose type is Texture&& is an lvalue expression when used by name.

Forwarding references and reference collapsing

template <typename T>
void Register(T&& value)
{
    Store(value);
}

When T is deduced, this T&& is a forwarding reference. Passing an lvalue deduces an lvalue-reference form of T; passing an rvalue retains an rvalue reference after reference collapsing.

Inside the function, however, value has a name, so Store(value) always chooses the lvalue overload.

Restore the original category with std::forward

#include <utility>

template <typename T>
void Register(T&& value)
{
    Store(std::forward<T>(value));
}

std::forward<T> forwards an original lvalue as an lvalue and an original rvalue as an rvalue. Unlike std::move, it does not unconditionally cast to an rvalue.

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

Factories use this pattern to relay constructor arguments without inventing copies.

Not every API needs forwarding references

For a setter that stores a string, taking by value and moving once is often simpler.

void Player::SetName(std::string name)
{
    Name = std::move(name);
}

Forwarding references accept a very broad overload set and can produce difficult diagnostics. Use them in genuinely transparent generic adapters. At application boundaries, prefer a concrete type and an explicit ownership contract.