[2026] C++ decltype | Extract Expression Types
이 글의 핵심
decltype vs auto, decltype(auto), trailing return types, SFINAE with decltype, and the decltype(x) vs decltype((x)) pitfall for templates.
What is decltype?
decltype yields the type of an expression. Unlike plain auto in many cases, it can preserve top-level const and reference.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 변수 선언 및 초기화
int x = 10;
decltype(x) y = 20; // int
const int& ref = x;
decltype(ref) z = x; // const int&
Key properties:
- Unevaluated context (expression not executed)
- Preserves cv-qualifiers and references
- Different rules for id-expressions vs general expressions
auto vs decltype
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
const int& r = x;
auto a = r; // int (decay: removes const and reference)
decltype(r) b = r; // const int& (preserves everything)
int arr[5];
auto a2 = arr; // int* (decay to pointer)
decltype(arr) a3; // int[5] (preserves array type)
Comparison table:
| Feature | auto | decltype |
|---|---|---|
| Removes const | Yes | No |
| Removes reference | Yes | No |
| Array decay | Yes | No |
| Function decay | Yes | No |
Trailing return types (C++11)
Before C++14, auto with trailing return was needed for dependent types:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 실행 예제
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// Usage
auto result = add(1, 2.5); // double
C++14 improvement: Plain auto with return type deduction:
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T, typename U>
auto add(T a, U b) {
return a + b; // Type deduced automatically
}
decltype(auto) (C++14)
Combines auto deduction with decltype rules:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int& getRef();
const int& getConstRef();
auto a = getRef(); // int (decay)
decltype(auto) b = getRef(); // int& (exact type)
auto c = getConstRef(); // int (decay)
decltype(auto) d = getConstRef(); // const int& (exact type)
Perfect return type forwarding
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename Func, typename....Args>
decltype(auto) call(Func&& f, Args&&....args) {
// Returns exact type from f, including references
return std::forward<Func>(f)(std::forward<Args>(args)...);
}
int& getInt();
decltype(auto) result = call(getInt); // int&, not int
SFINAE with decltype
Detecting member functions
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <type_traits>
template<typename T>
auto process(T v) -> decltype(v.size(), void()) {
std::cout << "Has size(): " << v.size() << "\n";
}
template<typename T>
void process(...) {
std::cout << "No size()\n";
}
// Usage
std::vector<int> vec = {1, 2, 3};
process(vec); // "Has size(): 3"
process(42); // "No size()"
Using std::declval
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <utility>
template<typename T>
auto getValue(T& container)
-> decltype(std::declval<T>()[0]) {
return container[0];
}
// Works with any type that supports operator[]
std::vector<int> vec = {10, 20};
auto val = getValue(vec); // int&
decltype(x) vs decltype((x))
Critical difference:
int x = 10;
decltype(x) a; // int (id-expression: declared type)
decltype((x)) b = x; // int& (expression: lvalue reference)
Rules
| Expression | Category | decltype result |
|---|---|---|
x (id-expression) | - | Declared type |
(x) (parenthesized) | lvalue | T& |
std::move(x) | xvalue | T&& |
42 | prvalue | T |
Real-world pitfall
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
decltype(auto) forward(T& x) {
return (x); // ❌ Returns reference! Dangling if x is local
}
template<typename T>
decltype(auto) forward(T& x) {
return x; // ✅ Returns by value
}
Real-world applications
1. Generic lambda return type
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
auto lambda = [](auto x, auto y) -> decltype(x + y) {
return x + y;
};
auto result1 = lambda(1, 2); // int
auto result2 = lambda(1.5, 2); // double
auto result3 = lambda(std::string("a"), std::string("b")); // std::string
2. CRTP base class
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename Derived>
class Base {
public:
decltype(auto) interface() {
return static_cast<Derived*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
int& implementation() {
static int value = 42;
return value;
}
};
Derived d;
int& ref = d.interface(); // Returns int&, not int
3. Perfect proxy
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
class Proxy {
T* ptr_;
public:
Proxy(T* p) : ptr_(p) {}
template<typename....Args>
decltype(auto) operator()(Args&&....args) {
// Forwards exact return type from T::operator()
return (*ptr_)(std::forward<Args>(args)...);
}
};
Common mistakes
Mistake 1: Dangling reference with decltype(auto)
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
decltype(auto) getLocal() {
int x = 10;
return (x); // ❌ Returns int&, but x is destroyed!
}
// ✅ Fix: remove parentheses
decltype(auto) getLocal() {
int x = 10;
return x; // Returns int (copy)
}
Mistake 2: Unnecessary decltype in variable declarations
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int x = 10;
decltype(x) y = 20; // ❌ Verbose, just use int
// ✅ Better
int y = 20;
// decltype useful when type is complex
std::vector<int> vec;
decltype(vec.begin()) it = vec.begin(); // ✅ Good use case
Mistake 3: Confusing decltype(auto) with auto&&
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int x = 10;
auto&& a = x; // int& (forwarding reference)
decltype(auto) b = x; // int (copy)
decltype(auto) c = (x); // int& (reference)
Type inspection at compile time
Print type with error
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
struct TD; // Type Displayer (intentionally undefined)
int x = 10;
TD<decltype(x)> xType; // Error shows: TD<int>
TD<decltype((x))> xRefType; // Error shows: TD<int&>
Runtime type info
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
#include <typeinfo>
#include <iostream>
int x = 10;
std::cout << typeid(decltype(x)).name() << "\n"; // "i" (int)
std::cout << typeid(decltype((x))).name() << "\n"; // "i" (reference removed)
// Better: use boost::core::demangle or compiler-specific
Performance implications
Zero overhead: decltype is purely compile-time. No runtime cost.
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Both produce identical assembly
int x = 10;
int y = x; // Direct
decltype(x) z = x; // Via decltype
Advanced: decltype in SFINAE
Detecting operator overloads
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T, typename U>
auto canAdd(int) -> decltype(std::declval<T>() + std::declval<U>(), std::true_type{});
template<typename T, typename U>
std::false_type canAdd(...);
// Usage
static_assert(decltype(canAdd<int, int>(0))::value); // true
static_assert(!decltype(canAdd<int, std::string>(0))::value); // false
Expression validity
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
struct has_size {
private:
template<typename U>
static auto test(int) -> decltype(std::declval<U>().size(), std::true_type{});
template<typename>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
static_assert(has_size<std::vector<int>>::value);
static_assert(!has_size<int>::value);
C++20 improvements
Concepts replace many decltype SFINAE patterns
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C++11-17: decltype SFINAE
template<typename T>
auto process(T v) -> decltype(v.size(), void());
// C++20: Concepts
template<typename T>
requires requires(T v) { v.size(); }
void process(T v);
Compiler support
| Compiler | decltype | decltype(auto) | Notes |
|---|---|---|---|
| GCC | 4.3+ | 4.9+ | Full support |
| Clang | 2.9+ | 3.3+ | Excellent diagnostics |
| MSVC | 2010+ | 2015+ | Some early bugs |
Related posts
Keywords
C++, decltype, decltype(auto), trailing return type, templates, type deduction, SFINAE, C++11, C++14