Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A C++ reference parameter is an alias for an existing object passed as an argument to a function. Instead of creating a local copy of the argument (pass-by-value), a reference parameter binds directly to the memory location of the caller’s argument, allowing the function to operate directly on the original data without explicit pointer dereferencing. Under the hood, compilers typically implement references as constant pointers. However, at the language level, references are not objects; they do not have their own memory addresses, cannot be null, and cannot be re-bound to a different object once initialized by the function call.

Syntax and Mechanics

A reference parameter is declared by appending an ampersand (&) for an lvalue reference, or a double ampersand (&&) for an rvalue reference, to the type specifier in the function signature.
// 'val' is an lvalue reference parameter
void mutateData(int& val) {
    val = 42; // Implicitly modifies the caller's original variable
}

int main() {
    int x = 10;
    mutateData(x); // x is now 42
    return 0;
}

Types of Reference Parameters

Reference parameters are categorized based on the value category (lvalue vs. rvalue) and const-qualification of the arguments they can bind to.

1. Lvalue Reference (T&)

Binds exclusively to modifiable lvalues (objects that occupy identifiable memory locations). It cannot bind to literals, temporaries, or const objects.
#include <string>

void processLvalue(std::string& str) {}

int main() {
    std::string data = "text";
    processLvalue(data);       // Valid: 'data' is a modifiable lvalue
    // processLvalue("text");  // Error: Cannot bind non-const lvalue reference to an rvalue
    return 0;
}

2. Const Lvalue Reference (const T&)

Binds to modifiable lvalues, non-modifiable (const) lvalues, and rvalues (temporaries). When bound to a temporary object during a function call, temporary lifetime extension explicitly does not apply to the function parameter. Instead, the temporary naturally persists until the end of the full-expression (typically the end of the statement) containing the function call.
#include <string>

void processConst(const std::string& str) {}

int main() {
    std::string data = "text";
    processConst(data);        // Valid: Binds to lvalue
    processConst("temporary"); // Valid: Binds to rvalue (implicit std::string temporary)
                               // The temporary is destroyed at the end of this statement.
    return 0;
}

3. Rvalue Reference (T&&)

Introduced in C++11, this parameter binds exclusively to rvalues (temporary objects or objects explicitly cast to rvalues via std::move).
#include <string>
#include <utility>

void processRvalue(std::string&& str) {}

int main() {
    std::string data = "text";
    // processRvalue(data);            // Error: Cannot bind rvalue reference to an lvalue
    processRvalue("temporary");        // Valid: Binds to rvalue
    processRvalue(std::move(data));    // Valid: Binds to xvalue (explicitly moved lvalue)
    return 0;
}

Forwarding References (T&& or auto&&)

When T&& is used in a template function where T is a deduced type parameter, it acts as a forwarding reference (sometimes called a “universal reference”). Through reference collapsing rules, it can bind to both lvalues and rvalues, preserving the value category of the original argument. To actually utilize this preserved value category when passing the parameter to another function, std::forward is required. std::forward conditionally casts the parameter to an rvalue only if it was originally passed as an rvalue. Additionally, auto&& acts as a forwarding reference because it relies on the exact same type deduction and reference collapsing rules. This is commonly used in generic lambdas or range-based for loops.
#include <utility>

void targetFunction(int& val) {}
void targetFunction(int&& val) {}

template <typename T>
void forwardData(T&& param) {
    // std::forward preserves the value category of 'param'
    targetFunction(std::forward<T>(param));
}

int main() {
    int x = 10;
    
    // T is deduced as int&. T&& collapses to int&.
    forwardData(x);  // Calls targetFunction(int&)
    
    // T is deduced as int. T&& becomes int&&.
    forwardData(20); // Calls targetFunction(int&&)
    
    // auto&& as a forwarding reference in a generic lambda
    auto genericLambda = [](auto&& arg) {
        targetFunction(std::forward<decltype(arg)>(arg));
    };
    
    genericLambda(x);  // Binds as lvalue reference
    genericLambda(30); // Binds as rvalue reference
    
    return 0;
}
Master C++ with Deep Grasping Methodology!Learn More