Perfect Forwarding

Perfect forwarding preserves the value category (lvalue/rvalue) of function arguments. Using a universal reference (template typename T&&) and std::forward<T>, a function template can pass arguments to another function exactly as they were received. This avoids unnecessary copies when forwarding temporaries and maintains the ability to modify lvalues. It's the cornerstone of factory functions, emplacement in containers, and wrapper functions that need to transparently pass arguments through multiple layers.

⚡ What is Perfect Forwarding?

Perfect forwarding preserves the value category (lvalue/rvalue) of arguments when passing them to other functions, enabling efficient template programming without unnecessary copies.


// Perfect forwarding example
template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}
                                    

Key Concepts

🔄

Universal References

T&& in template contexts

template<typename T>
void func(T&& param) {}
➡️

std::forward

Preserves value categories

std::forward<T>(param)
🎯

Reference Collapsing

Rules for reference types

T& && → T&
T&& && → T&&

Move Semantics

Efficient resource transfer

std::move(object)

🔹 Basic Perfect Forwarding

Perfect forwarding preserves the value category (lvalue or rvalue) of arguments when passing them through wrapper functions. It uses universal references (T&&) with std::forward to maintain original argument types. For example, template<typename T> void wrapper(T&& arg) { func(std::forward<T>(arg)); } ensures func receives the argument exactly as provided. This technique is essential for generic libraries, enabling efficient move semantics and avoiding unnecessary copies in template functions.

#include <iostream>
#include <utility>

void process(int& x) {
    std::cout << "Lvalue: " << x << std::endl;
}

void process(int&& x) {
    std::cout << "Rvalue: " << x << std::endl;
}

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int x = 42;
    wrapper(x);        // Calls lvalue version
    wrapper(100);      // Calls rvalue version
    return 0;
}

Output:

Lvalue: 42
Rvalue: 100

🔹 Factory Function Example

Factory functions use perfect forwarding to construct objects with arbitrary arguments efficiently. A generic factory like template<typename T, typename... Args> T create(Args&&... args) forwards all parameters to T’s constructor using std::forward<Args>(args).... This supports both lvalues and rvalues, enabling move semantics where possible. It is widely used in dependency injection, object pools, and builder patterns to centralize object creation while maintaining optimal performance and flexibility.

#include <memory>
#include <string>

class Person {
public:
    Person(const std::string& name, int age) 
        : name_(name), age_(age) {}
    
    Person(std::string&& name, int age) 
        : name_(std::move(name)), age_(age) {}
        
private:
    std::string name_;
    int age_;
};

template<typename... Args>
std::unique_ptr<Person> makePerson(Args&&... args) {
    return std::make_unique<Person>(std::forward<Args>(args)...);
}

int main() {
    std::string name = "Alice";
    auto p1 = makePerson(name, 25);        // Copy name
    auto p2 = makePerson("Bob", 30);       // Move temporary
    return 0;
}

🔹 Variadic Template Forwarding

Variadic templates with perfect forwarding handle functions with a variable number of arguments while preserving their value categories. The syntax template<typename... Args> void log(Args&&... args) accepts any number of arguments and forwards them to another function, such as std::format. This is common in logging, emplacement, and wrapper libraries where argument count and types are unknown. It ensures efficiency by avoiding copies and enabling perfect forwarding for each argument individually.

#include <iostream>
#include <utility>

template<typename Func, typename... Args>
auto call_function(Func&& f, Args&&... args) {
    std::cout << "Calling function with " 
              << sizeof...(args) << " arguments\n";
    return f(std::forward<Args>(args)...);
}

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = call_function(add, 5, 10);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

Output:

Calling function with 2 arguments
Result: 15

🧠 Test Your Knowledge

What does std::forward do?