Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're a high-end hotel concierge. A guest hands you something to give to the kitchen. If the guest hands you a temporary voucher that's only valid for one use (an rvalue), you want to hand that voucher to the chef so they can "consume" it. But if the guest hands you their permanent gold-member ID card (an lvalue), you just show it to the chef—you definitely don't try to "consume" or destroy the card. Your job as the concierge is to pass the item along exactly as it was given to you, without changing its nature.

In C++, this is exactly what perfect forwarding does. When you write a wrapper function—like a factory or a logger—that passes arguments to another function, you want to preserve whether those arguments were temporary objects or persistent variables. If you don't, you'll either end up with unnecessary, expensive copies or, worse, you'll accidentally move from an object that the caller still needs.

The Trap of Named Variables

You might think that using a template with T&& is enough to handle everything. But here is the catch that trips up almost everyone: once a variable has a name, it is an lvalue. Period.

template <typename T>
void wrapper(T&& arg) {
    // Even if 'arg' was passed as an rvalue, 
    // 'arg' itself has a name here, so it's an lvalue.
    target_function(arg); 
}

In the code above, if I pass a temporary string to wrapper, T becomes std::string and arg is an rvalue reference. But when I call target_function(arg), I'm passing a named variable. C++ sees that name and says, "This is an lvalue." Consequently, target_function will call its const-reference overload (copying) instead of its rvalue overload (moving). We've lost the "temporary-ness" of the object.

Restoring the Original State with std::forward

This is where std::forward comes in. It's not actually "forwarding" in the way a post office does; it's more like a conditional cast. It says: "If the original argument passed to the wrapper was an rvalue, cast this named variable back into an rvalue. If it was an lvalue, leave it alone."

Let's look at a real example. Suppose we have a Widget class that has a heavy constructor and a move constructor we want to leverage.

class Widget {
public:
    Widget(const std::string& s) { std::cout < "Copying string\n"; }
    Widget(std::string&& s) { std::cout < "Moving string\n"; }
};

template <typename T>
Widget make_widget(T&& arg) {
    // std::forward<T> restores the value category
    return Widget(std::forward<T>(arg));
}

// Usage:
std::string name = "MyWidget";
make_widget(name);            // Passes lvalue -> calls Copy constructor
make_widget(std::string("Temp")); // Passes rvalue -> calls Move constructor

I've used T&& here, which in a template context is called a "forwarding reference" (or a universal reference). It's a special rule in the C++ compiler: if T is a deduced template type, T&& can bind to anything. Pair that with std::forward, and you've built a perfect pipeline.

When to actually use this

I'll be honest with you: you won't use perfect forwarding in every function. It's specifically for "intermediary" functions. If you're writing a function that actually uses the data, just use const T& or T&&. But if you're writing a function whose primary job is to pass arguments to another function (like std::make_shared or a custom factory), perfect forwarding is the only way to ensure you aren't killing your program's performance with stealthy copies.




📋 Practical Task

Implement a Generic Event Dispatcher Wrapper

You are building an event system. You have a LogEvent class that takes a std::string message. To avoid unnecessary allocations, LogEvent has two constructors: one for lvalues (copy) and one for rvalues (move).

Your task is to create a template function called dispatch_log that acts as a wrapper. This function should take any argument and "perfectly forward" it to the LogEvent constructor.

Requirements:

  • Define a LogEvent class with:
    • A constructor LogEvent(const std::string& msg) that prints "LogEvent: Copied".
    • A constructor LogEvent(std::string&& msg) that prints "LogEvent: Moved".
  • Implement the dispatch_log template function using a forwarding reference and std::forward.
  • In main, call dispatch_log twice: once with a variable (lvalue) and once with a string literal or temporary std::string (rvalue).
  • Verify that the output shows "Copied" for the variable and "Moved" for the temporary.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.