[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::movein a forwarding wrapper forces rvalue—usestd::forward<T>when preserving category.decltype((x))can introduce references—pair withremove_reference_twhen 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
| T | T& | T&& | Use case |
|---|---|---|---|
int | int& | 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
Related posts
Keywords
C++, reference collapsing, forwarding reference, templates, C++11, type deduction, perfect forwarding, std::forward