[2026] C++ Reference Collapsing | Rules for T& and T&&

[2026] C++ Reference Collapsing | Rules for T& and T&&

이 글의 핵심

Reference collapsing: how T&, T&& combinations collapse to a single reference, enabling forwarding references, std::forward, and template deduction.

What is reference collapsing?

You cannot write int& & directly, but nested references can appear through aliases, templates, and decltype. The compiler collapses them: 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

T&  &   -> T&
T&  &&  -> T&
T&& &   -> T&
T&& &&  -> T&&

Rule of thumb: if any & is involved, the result is &; only && + && stays &&.

With forwarding references

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void func(T&& arg) {}
int x = 10;
func(x);       // T = int&,   T&& -> int& && -> int&
func(10);      // T = int,    T&& -> int&&

auto&&

int x = 10;
auto&& a = x;           // int& (collapsing)
auto&& b = 10;          // int&&

std::forward sketch

다음은 간단한 cpp 코드 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
T&& forward(typename std::remove_reference_t<T>& arg) noexcept {
    return static_cast<T&&>(arg);
}

Pitfalls

  • std::move in a forwarding wrapper forces rvalue—use std::forward<T> when preserving category.
  • decltype((x)) can introduce references—pair with remove_reference_t when needed.

Deep dive: Why collapsing exists

Problem without collapsing

Before C++11, you couldn’t have references to references:

typedef int& IntRef;
typedef IntRef& IntRefRef;  // ❌ Error in C++03

Solution: Collapsing rules

C++11 introduced reference collapsing to make templates work with references: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void func(T&& arg);  // T can be int, int&, or int&&
int x = 10;
func(x);  // T = int&, arg type = int& && -> int&

Without collapsing, T = int& would make T&& illegal (int& &&).

Complete collapsing table with examples

TT&T&&Use case
intint&int&&Normal types
int&int&int&Lvalue passed to template
int&&int&int&&Rvalue passed to template
const int&const int&const int&Const lvalue

Practical example

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
void wrapper(T&& arg) {
    std::cout << std::boolalpha;
    std::cout << "Is lvalue ref: " << std::is_lvalue_reference_v<decltype(arg)> << "\n";
    std::cout << "Is rvalue ref: " << std::is_rvalue_reference_v<decltype(arg)> << "\n";
}
int x = 10;
wrapper(x);        // T=int&,  arg=int&   -> lvalue ref: true
wrapper(10);       // T=int,   arg=int&&  -> rvalue ref: true
wrapper(std::move(x));  // T=int, arg=int&& -> rvalue ref: true

Interaction with std::forward

How forward uses collapsing

아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
T&& forward(std::remove_reference_t<T>& arg) noexcept {
    return static_cast<T&&>(arg);
}
// When T = int& (lvalue case):
// int& && forward(int& arg) -> int& forward(int& arg)
// Returns: static_cast<int& &&>(arg) -> static_cast<int&>(arg)
// When T = int (rvalue case):
// int&& forward(int& arg)
// Returns: static_cast<int&&>(arg)

Real-world forwarding example

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
class Optional {
    alignas(T) unsigned char storage_[sizeof(T)];
    bool hasValue_ = false;
    
public:
    template<typename U>
    void emplace(U&& value) {
        if (hasValue_) {
            reinterpret_cast<T*>(storage_)->~T();
        }
        // Collapsing ensures correct construction:
        // U=int&  -> new(storage_) T(static_cast<int&>(value))
        // U=int   -> new(storage_) T(static_cast<int&&>(value))
        new(storage_) T(std::forward<U>(value));
        hasValue_ = true;
    }
};

decltype and collapsing

decltype((x)) adds reference

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int x = 10;
decltype(x) a = x;      // int
decltype((x)) b = x;    // int& (extra parens make it lvalue expression)
template<typename T>
void func(T&& arg) {
    decltype(arg) local = arg;  // Preserves reference type
}

Pitfall: Double reference

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void process(T&& arg) {
    using RefType = decltype(arg);  // Could be int& or int&&
    RefType&& x = std::forward<T>(arg);  // ❌ Potential double reference
}

Fix: Use std::remove_reference_t: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void process(T&& arg) {
    using ValueType = std::remove_reference_t<decltype(arg)>;
    ValueType copy = arg;  // Always makes a copy
}

Common mistakes

Mistake 1: Assuming T&& is always rvalue reference

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
void bad(T&& arg) {
    // ❌ Wrong assumption: arg might be lvalue reference
    std::vector<T> vec;
    vec.push_back(std::move(arg));  // Moves even from lvalue!
}
template<typename T>
void good(T&& arg) {
    std::vector<std::remove_reference_t<T>> vec;
    vec.push_back(std::forward<T>(arg));  // ✅ Preserves category
}

Mistake 2: Forgetting collapsing in type aliases

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
struct Wrapper {
    using RefType = T&&;  // Not always rvalue reference!
};
Wrapper<int&>::RefType x;  // int& (not int& &&)

Mistake 3: auto&& in wrong context

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::vector<int> getVec();
auto&& vec = getVec();  // ✅ OK: extends lifetime of temporary
auto&& first = vec[0];  // ✅ OK: binds to lvalue element
// ❌ Dangerous:
auto&& dangling = getVec()[0];  // Temporary vector destroyed!

Performance implications

Zero overhead: Reference collapsing is compile-time only. No runtime cost. Code generation comparison (GCC 13, -O3): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Hand-written overloads
void process(int& x) { /* ....*/ }
void process(int&& x) { /* ....*/ }
// Forwarding reference
template<typename T>
void process(T&& x) { /* ....*/ }

Both generate identical assembly after template instantiation.

Advanced: Collapsing with variadic templates

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename....Args>
void multiForward(Args&&....args) {
    // Each arg follows collapsing rules independently
    callee(std::forward<Args>(args)...);
}
int x = 10;
multiForward(x, 20, std::string("hello"));
// Args = int&, int, std::string
// args = int&, int&&, std::string&&

Debugging variadic collapsing

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
void printType() {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}
template<typename....Args>
void debug(Args&&....args) {
    (printType<Args>(), ...);  // C++17 fold expression
}
int x = 10;
debug(x, 20);
// Prints:
// void printType() [T = int&]
// void printType() [T = int]

Compiler diagnostics

GCC/Clang: View collapsed types

# Compile with -fdump-tree-original to see template instantiations
g++ -std=c++20 -fdump-tree-original test.cpp

MSVC: Template instantiation details

# Use /d1reportAllClassLayout to see all template expansions
cl /std:c++20 /d1reportAllClassLayout test.cpp

Keywords

C++, reference collapsing, forwarding reference, templates, C++11, type deduction, perfect forwarding, std::forward

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3