decltype은 주어진 이름이나 표현식의 형식을 알려준다.
대부분의 경우 decltype은 주어진 이름이나 표현식의 구체적인 형식을 그대로 말해준다.
const int i = 0; // decltype(i)는 const int
bool f(const Widget& w); // decltype(w)는 const Widget&, decltype(f)는 bool(const Widget&)
struct Point {
int x, y; // decltype(Point::x) 는 int, decltype(Point::y)는 int
};
Widget w; // decltype(w)는 Widget
if (f(w)) ... // decltype(f(w))는 bool
template<typename T> // std::vector를 단순화한 버전
class vector {
public:
...
T& operator[](std::size_t index);
...
};
vector<int> v; // decltype(v)는 vector<int>
...
if (v[0] == 0)... // decltype(v[0])은 int
C++11에서는 decltype은 함수의 반환 형식이 그 매개변수 형식들에 의존하는 함수 템플릿을 선언할 때 주로 쓰인다.
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
함수 이름 앞의 auto는 C++11의 후행 반환 형식 구문이 쓰인다는 점을 나타낸다. 즉 함수의 반환 형식을 이 위치가 아니라 매개변수 목록 다음에 선언하겠다는 것을 나타내는 것 뿐이다.
C++11은 람다 함수가 한 문장으로 이루어져 있다면 그 반환 형식의 연역을 허용하며, C++14는 허용 범위를 더 확장해서 모든 람다와 모든 함수의 반환 형식 연역을 허용한다.
template<typename Container, typename Index> // C++14, 아주 정확하진 않음
auto authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i]; // 반환 형식은 c[i]로부터 연역된다.
}
함수 반환 형식에 auto가 지정되어 있으면 컴파일러는 템플릿 형식 연역을 적용한다.
문제는, 템플릿 형식 연역 과정에서 초기화 표현식의 참조성이 무시된다는 점이다.
std::deque<int> d;
authAndAccess(d, 5) = 10; // 컴파일 되지 않는다.
d[5]가 int&를 돌려주지만 auto 반환 형식 연역 과정에서 참조성이 무시되어 최종 반환 형식은 int가 된다. 반환값으로서의 int는 오른값이며, 오른값 int에 10을 배정하는 것은 금지되어 있기 때문에 컴파일 되지 않는다.
template<typename Container, typename Index> // C++14
decltype(auto) authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
이제 authAndAccess의 반환 형식은 실제로 c[i]의 반환 형식과 일치한다.
decltype(auto)를 변수 선언할 때에도 초기화 표현식에 decltype 형식 연역규칙들을 적용하고 싶을 경우 사용할 수 있다.
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto 형식 연역, myWidget1의 형식은 Widget
decltype(auto) myWidget2 = cw; // decltype 형식 연역, myWidget2의 형식은 const Widget&
다시 C++14 버전의 선언으로 돌아와서,
컨테이너 c는 비const 객체에 대한 왼값 참조로서 함수에 전달된다. 하지만 왼값뿐만 아니라 오른값도 받아들이도록 하기 위해 보편 참조를 사용한다.
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i) // 최종 C++14 버전
{
authenticateUser();
return std::forward<Container>(c)[i]; // 보편 참조에 std::forward 적용
}
template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i) // 최종 C++11 버전
-> decltype(std::forward<Container>(c)[i])
{
authenticateUser();
return std::forward<Container>(c)[i];
}
decltype에 이름을 적용하면 그 이름에 대해 선언된 형식이 산출된다. 대체로 이름은 왼값 표현식이다. 하지만 이름보다 복잡한 왼값 표현식에 대해서는 일반적으로 decltype은 왼값 참조를 보고한다. 즉 이름이 아닌, 형식이 T인 왼값 표현식에 대해 decltype은 T&를 보고한다.
int x = 0; // x는 변수의 이름이므로 decltype(x)는 int
int(x) = 0; // x는 하나의 왼값, (x)라는 표현식도 왼값이다. 즉 decltype((x))는 int&
decltype(auto)는 보고하는 형식에 영향을 미칠 수 있기 때문에 사용에 주의해야 한다.
Conclusion
- decltype은 항상 변수나 표현식의 형식을 아무 수정없이 보고한다.
- decltype은 형식이 T이고 이름이 아닌 왼값 표현식에 대해서는 항상 T&형식을 보고한다.
- C++14는 decltype(auto)를 지원한다. auto처럼 초기치로부터 형식을 연역하지만 그 과정에서 decltype 규칙을 적용한다.
Reference
Effective Modern C++ 항목 3: decltype의 작동방식을 숙지하라
'C++ > Effective Modern C++' 카테고리의 다른 글
auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용 (0) | 2020.06.28 |
---|---|
명시적 형식 선언보다는 auto를 선호하라 (0) | 2020.06.27 |
연역된 형식을 파악하는 방법 (0) | 2020.06.26 |
auto 형식 연역 규칙 (0) | 2020.06.24 |
템플릿 형식 연역 규칙 (0) | 2020.06.23 |
댓글