decltype 지정자
decltype 지정자 (C++11)
decltype은 엔티티(entity)의 선언된 타입(declared type)을 조사해요. 표현식이라면 그 표현식의 타입과 값 범주(value category)까지 함께 알려주죠. 우리가 손으로 쓰기 어렵거나 아예 못 쓰는 타입, 그러니까 람다 관련 타입이나 템플릿 매개변수에 의존하는 타입을 다룰 때 특히 유용한 키워드예요.
이 절의 목표는 decltype이 어떤 규칙으로 타입을 뽑아내는지를 정확히 이해하는 거예요. 공식 언어 레퍼런스 수준으로 규칙을 짚어볼게요.
출처: cppreference
본문
문법 (Syntax)
decltype은 인자 하나를 괄호로 감싸서 써요.
| 표기 | 의미 |
|---|---|
decltype(entity) |
(1) 엔티티의 선언된 타입을 조사 |
decltype(expression) |
(2) 표현식의 타입과 값 범주를 조사 |
설명 (Explanation)
먼저 인자가 괄호로 묶지 않은 id-expression인 경우예요. 여기서 id-expression이라는 건 이름을 가리키는 표현식을 뜻해요. 괄호 없이 이름을 그대로 넘기면 decltype은 그 이름이 가리키는 엔티티의 타입을 돌려줘요. 다만 그런 엔티티가 없거나, 인자가 오버로드된 함수들의 집합을 가리키면 프로그램은 ill-formed(형식에 어긋남)가 돼요.
버전에 따라 괄호 없는 id-expression이 여러 가지 특별한 대상을 가리킬 때가 있어요.
- 구조적 바인딩(structured binding)을 가리키는 경우 (C++17부터):
decltype은 그 구조적 바인딩의 참조형(referenced type) 을 돌려줘요. 이 타입은 구조적 바인딩 선언의 명세에 설명돼 있어요. - 비타입 템플릿 매개변수(non-type template parameter)를 가리키는 경우 (C++20부터): 템플릿 매개변수의 타입을 돌려줘요. 매개변수가 자리표시자 타입(placeholder type)으로 선언됐다면 필요한 타입 추론을 먼저 수행해요. 그리고 엔티티가 템플릿 매개변수 객체(그 자체는 const 객체)라도, 돌려주는 타입은 const가 아니에요.
- 연결(splice) 표현식을 가리키는 경우 (C++26부터): 그 표현식이 가리키는 엔티티·객체·값의 타입을 돌려줘요.
그 다음이 인자가 그 밖의 다른 표현식인 경우예요. 표현식의 타입을 T라고 할 때, 값 범주에 따라 이렇게 나뉘어요.
- a)
expression의 값 범주가 xvalue이면decltype은T&&를 돌려줘요. - b) 값 범주가 lvalue이면
decltype은T&를 돌려줘요. - c) 값 범주가 prvalue이면
decltype은T를 돌려줘요.
여기서 임시 객체가 만들어지는지가 버전에 따라 달라져요.
- (C++17 이전)
expression이 클래스 타입의 prvalue를 돌려주는 함수 호출이거나, 그런 함수 호출을 오른쪽 피연산자로 두는 콤마 표현식이라면, 그 prvalue에 대해 임시 객체가 생기지 않아요. - (C++17부터)
expression이 (괄호로 묶였을 수 있는) 즉시 호출(immediate invocation)을 제외한 prvalue라면, 그 prvalue에서 임시 객체가 materialize되지 않아요. 즉 그런 prvalue에는 결과 객체(result object)가 없어요.
꼭 기억해 둘 점이 하나 있어요. 객체의 이름을 괄호로 감싸면 보통의 lvalue 표현식으로 취급돼요. 그래서 decltype(x)와 decltype((x))는 서로 다른 타입이 되는 경우가 아주 흔해요.
decltype은 앞서 말했듯 표준 표기로는 선언하기 어렵거나 불가능한 타입을 선언할 때 유용해요. 대표적으로 람다 관련 타입이나 템플릿 매개변수에 의존하는 타입이 그렇죠.
참고 (Notes)
decltype의 존재를 감지하는 기능 검사 매크로(feature-test macro)는 다음과 같아요.
| 기능 검사 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_decltype |
200707L | (C++11) | decltype |
키워드 (Keywords)
decltype은 C++의 키워드예요.
예제 (Example)
decltype의 규칙을 코드로 직접 확인해 볼게요. 여기서는 두 종류의 핵심 규칙, 즉 선언된 타입과 lvalue 표현식의 타입이 어떻게 달라지는지를 봐요.
#include <cassert>
#include <iostream>
#include <type_traits>
struct A { double x; };
const A* a;
decltype(a->x) y; // y의 타입은 double (선언된 타입)
decltype((a->x)) z = y; // z의 타입은 const double& (lvalue 표현식)
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // 반환 타입이 템플릿 매개변수에 의존
// C++14부터는 반환 타입 추론이 가능
{
return t + u;
}
const int& getRef(const int* p) { return *p; }
static_assert(std::is_same_v<decltype(getRef), const int&(const int*)>);
auto getRefFwdBad(const int* p) { return getRef(p); }
static_assert(std::is_same_v<decltype(getRefFwdBad), int(const int*)>,
"Just returning auto isn't perfect forwarding.");
decltype(auto) getRefFwdGood(const int* p) { return getRef(p); }
static_assert(std::is_same_v<decltype(getRefFwdGood), const int&(const int*)>,
"Returning decltype(auto) perfectly forwards the return type.");
// 이렇게 써도 됩니다:
auto getRefFwdGood1(const int* p) -> decltype(getRef(p)) { return getRef(p); }
static_assert(std::is_same_v<decltype(getRefFwdGood1), const int&(const int*)>,
"Returning decltype(return expression) also perfectly forwards the return type.");
int main()
{
int i = 33;
decltype(i) j = i * 2;
static_assert(std::is_same_v<decltype(i), decltype(j)>);
assert(i == 33 && 66 == j);
auto f = [i](int av, int bv) -> int { return av * bv + i; };
auto h = [i](int av, int bv) -> int { return av * bv + i; };
static_assert(!std::is_same_v<decltype(f), decltype(h)>,
"The type of a lambda function is unique and unnamed");
decltype(f) g = f;
std::cout << f(3, 3) << ' ' << g(3, 3) << '\n';
}
출력:
42 42
예제에서 가장 인상적인 부분은 getRef를 돌려보내는 두 함수예요. auto로만 돌려주면 반환 타입이 int로 떨어져서 완벽 전달(perfect forwarding)이 되지 않고, decltype(auto)나 decltype(반환 표현식)을 쓰면 const int&가 그대로 보존돼요. 반환 타입의 자격(const 등)까지 그대로 전달하고 싶다면 decltype이 필요하다는 걸 보여주는 장면이에요.
더 알아보기 (Learn more)
- auto 지정자 (C++11) — 표현식에서 타입을 추론해요.
decltype과 함께 반환 타입 추론,decltype(auto)의 근간이 돼요. (cppreference) - declval (C++11) — 평가되지 않는 문맥(unevaluated context)에서 사용하기 위해 템플릿 타입 인자의 객체에 대한 참조를 얻는 함수 템플릿이에요. (cppreference)
- is_same (C++11) — 두 타입이 같은지 검사하는 클래스 템플릿이에요.
decltype의 결과를 검증할 때 자주 같이 써요. (cppreference) - underlying_type (C++11) — 주어진 열거형의 내부 정수 타입을 얻는 클래스 템플릿이에요. (cppreference)