OSC++.023: Perfect Forwarding — Forwarding References, std::forward, Reference Collapsing, and Universal Constructors

Colored-pencil illustration of a C++ developer workstation representing perfect forwarding, templates, and systems programming.

Elementary Overview

Perfect forwarding lets a C++ helper function pass an argument onward without accidentally changing whether the caller supplied an lvalue or an rvalue. It extends OSC++.022: Move Semantics: std::move says an object may be treated as movable, while std::forward preserves how an argument originally arrived. The mechanism combines function-template deduction, forwarding references, reference collapsing, and std::forward.

C++ on Sea — value categories, references, std::move, and std::forward.

Forwarding References

In template<class T> void relay(T&& value), T&& is a forwarding reference because T is a deduced, cv-unqualified function-template parameter. Passing an lvalue makes T deduce as an lvalue-reference type; passing an rvalue makes T deduce as the underlying type. A plain Widget&& and const T&& do not have this same behavior. This is generic-programming machinery, not merely another spelling of an rvalue reference.

CppCon — Back to Basics: Forwarding References.

Reference Collapsing

Template substitution can produce apparent references-to-references, which C++ reduces using reference-collapsing rules. The compact rule is && + && → &&; every combination containing an lvalue reference becomes &. Therefore an lvalue passed to a forwarding-reference parameter stays an lvalue reference, while an rvalue can remain an rvalue reference. Those rules are what allow one template to preserve two different caller categories.

Code for yourself — forwarding references and reference collapsing.
Generated FormCollapsed Type
T& &T&
T& &&T&
T&& &T&
T&& &&T&&

std::forward Preserves the Caller’s Category

A named function parameter is an lvalue expression inside the function body even if its type contains &&. Calling another function with the parameter name alone therefore loses the caller’s original rvalue-ness. std::forward<T>(value) conditionally restores the category described by the deduced T: lvalues stay lvalues and rvalues become rvalues again. This is why std::forward belongs in forwarding wrappers while std::move belongs where code intentionally permits moving from an object.

Detailed std::forward and perfect-forwarding walkthrough.
#include <utility>

template<class T>
void relay(T&& value) {
    consume(std::forward<T>(value));
}

Factories, Variadic Templates, and Constructor Pitfalls

Perfect forwarding is most useful when generic code receives arguments mainly to pass them somewhere else. Factories, emplacement functions, callback wrappers, and variadic templates can accept Args&&... and forward each argument with std::forward<Args>(args).... This connects to constructors and smart-pointer ownership. Forwarding constructors can also be too greedy, so production code often constrains them with concepts, type traits, or carefully designed overloads rather than assuming perfect forwarding is always the best interface.

BitsOfQ — variadic templates with perfect-forwarding usage.
template<class T, class... Args>
T make_object(Args&&... args) {
    return T(std::forward<Args>(args)...);
}

Worked Example

#include <iostream>
#include <string>
#include <utility>

void use(const std::string&) { std::cout << "lvalue path\n"; }
void use(std::string&&)      { std::cout << "rvalue path\n"; }

template<class T>
void wrapper(T&& value) {
    use(std::forward<T>(value));
}

int main() {
    std::string name = "Hash Race";
    wrapper(name);
    wrapper(std::string{"miner"});
}

Developer Checklist

  1. Confirm T&& is actually in a forwarding-reference deduction context.
  2. Remember that a named parameter is an lvalue expression inside the function.
  3. Use std::forward<T>(arg) to preserve the caller’s category.
  4. Use std::move only when moving is intentionally permitted.
  5. Forward every variadic argument with its matching template type.
  6. Constrain greedy forwarding constructors when necessary.
  7. Prefer pass-by-value or const& when they make the interface simpler.
  8. Test both lvalue and rvalue call sites.

Exercises

  1. Write a forwarding wrapper around lvalue and rvalue overloads.
  2. Remove std::forward and explain the changed behavior.
  3. Determine the deduced T when the caller supplies an lvalue.
  4. Determine the deduced T when the caller supplies an rvalue.
  5. Write a variadic factory that forwards constructor arguments.
  6. Explain why const T&& is not a forwarding reference.

Knowledge Check + Answers

  1. What does perfect forwarding preserve? The original value category of an argument.
  2. When is T&& a forwarding reference? When T is a deduced, cv-unqualified template parameter in the required context.
  3. What does T& && collapse to? T&.
  4. What does T&& && collapse to? T&&.
  5. Why is std::forward needed? A named parameter is an lvalue expression inside the wrapper.
  6. How does it differ from std::move? std::forward conditionally preserves the caller’s category; std::move unconditionally casts toward an rvalue/xvalue.

Reference Resources

Elementary Conclusion

Perfect forwarding is a way for a helper function to pass an object through without changing the caller’s original intent. If the caller gave the helper a normal named object, the next function should still see an lvalue; if the caller gave it a temporary object that can be moved from, the next function should still be allowed to see an rvalue. Template deduction and reference collapsing figure out the type, and std::forward restores the correct category at the next call. The easiest rule to remember is: std::move means “I am willing to move from this,” while std::forward means “keep treating this the way the caller gave it to me.”

CppCon — move-semantics fundamentals, including correct std::move and std::forward use.

BitcoinVersus.Tech

Advertisement

BitcoinVersus.Tech advertisement.

Editor’s Note:

We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our research initiatives, please donate here: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb

BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a comment