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