universal_reference


Concept: universal reference

A universal reference is a special type of reference in C++ that can bind to both lvalues and rvalues. Universal references are typically used in function templates and are identified by the syntax T&& when T is a template parameter and type deduction is in play. Universal references are a powerful tool in C++ that allow you to write generic code that works seamlessly with both lvalues and rvalues.

Key Characteristics of Universal References

How to Identify a Universal Reference

Universal references are identified by the combination of two factors: - Template Parameter Deduction: The reference type must involve a template parameter. - T&& Syntax: The reference type must be written as T&&.

For example, in the function template below, param is a universal reference:

template<typename T>
void func(T&& param) {
    // param is a universal reference
}

Universal References in Action

Let's explore some examples to understand how universal references work in practice.

Example 1: Universal References and Type Deduction

#include <iostream>

template<typename T>
void func(T&& param) {
    if constexpr (std::is_lvalue_reference<T>::value) {
        std::cout << "param is an lvalue reference\n";
    } else {
        std::cout << "param is an rvalue reference\n";
    }
}

int main() {
    int x = 42;
    func(x);          // x is an lvalue, so T deduces to int&
    func(42);         // 42 is an rvalue, so T deduces to int

    return 0;
}

Explanation:

Type Deduction and Reference Collapsing

C++ has rules for reference collapsing, which dictate how multiple reference qualifiers combine. The rules are: - T& & becomes T& - T& && becomes T&-T&& &becomesT&-T&& &&becomesT&&` - These rules ensure that universal references work correctly with both lvalues and rvalues.

Example 2: Perfect Forwarding with Universal References

Universal references are essential for perfect forwarding, where you want to pass arguments to another function while preserving their value category.

#include <iostream>
#include <utility>  // for std::forward

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

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

template<typename T>
void forwarder(T&& arg) {
    process(std::forward<T>(arg));  // Perfectly forward the argument
}

int main() {
    int a = 42;
    forwarder(a);         // Passes as lvalue
    forwarder(42);        // Passes as rvalue
    forwarder(std::move(a));  // Passes as rvalue

    return 0;
}

Explanation:

Universal References vs. Rvalue References

It's important to differentiate between universal references and rvalue references: - Rvalue References: Written as T&& when T is a concrete type (not a template parameter). Rvalue references can only bind to rvalues. - Universal References: Also written as T&&, but T is a template parameter. Universal references can bind to both lvalues and rvalues, depending on how T is deduced.

Example 3: Rvalue References vs. Universal References

#include <iostream>

// Rvalue reference
void rvalueRef(int&& param) {
    std::cout << "Rvalue reference\n";
}

// Universal reference
template<typename T>
void universalRef(T&& param) {
    std::cout << "Universal reference\n";
}

int main() {
    int x = 42;

    // rvalueRef(x);  // Error: x is an lvalue
    rvalueRef(42);    // OK: 42 is an rvalue

    universalRef(x);  // OK: x is an lvalue
    universalRef(42); // OK: 42 is an rvalue

    return 0;
}

Explanation:

Use Cases for Universal References

Summary

Universal references are a powerful feature in C++ that enable the creation of highly generic and efficient code. They are particularly useful in template programming and are foundational to modern C++ programming techniques like perfect forwarding.

Previous Page | Course Schedule | Course Content