[Modern C++] 항목 3: decltype의 작동 방식을 숙지하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
📦 1. 형식 연역
👉🏻 항목 3: decltype의 작동 방식을 숙지하라
decltype은 주어진 이름이나 표현식의 구체적인 형식을 그대로 말해준다.
const int i = 0; // decltype(i) = const int
// decltype(w) = const Widget&
// decltype(f) = bool(const Widget&)
bool f(const Widget& w);
// decltype(Point::x) = int
struct Point {
int x, y;
}
Widget w; // decltype(w) = Widget
if(f(w)) // decltype(f(w)) = bool
// std::vector 단순 버전
template<typename T>
class vector {
public:
...
T& operator[](std::size_t index);
...
};
vector<int> v; // decltype(v) = vector<int>
if(v[0] == 0) // decltype(v[0]) = int&
decltype은 함수의 반환 형식이 매개변수 형식들에 의존하는 함수 템플릿을 선언할 때 주로 쓰인다.- 컨테이너의
operator[]의 반환 형식이 컨테이너에 따라 다를 수 있다.- 형식
T객체를 담은 컨테이너에 대한operator[]연산은T&을 돌려준다. - 그러나,
vector<bool>에 대한operator[]연산은 새 객체를 돌려준다. decltype을 통해 해결할 수 있다.
- 형식
🔧 후행 반환 형식 구문
// C++11, 완벽 X, 정련 필요
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
// C++14, 완벽 X, 정련 필요
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
authenticateUser();
return c[i];
}
- 함수 이름 앞의
auto는 형식 연역과 상관이 없고, 후행 반환 형식 구문과 연관이 있다. - 후행 반환 형식 구문: 함수의 반환 형식을 매개변수 목록 다음에 선언하겠다는 뜻이다.
- C++11: 람다 함수가 한 문장이면, 반환 형식의 연역을 허용한다.
- C++14: 모든 람다와 함수의 반환 형식 연역을 허용한다. 후행 반환 형식을 생략하고
auto만 남겨도 된다.
deque<int> d;
authAndAccess(d, 5) = 10; // ⚠️ 컴파일 불가
// deque<int> d가 들어갔다 가정
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
authenticateUser();
// ⚠️ 에러 발생!
// c[i] = int&, return c[i] = int
return c[i];
}
- 항목 2에서 함수의 반환 형식에
auto가 있으면, 컴파일러는 템플릿 형식 연역을 적용한다.- 초기화 표현식의 참조성이 무시되어,
T&이 아닌T가 반환된다.
- 초기화 표현식의 참조성이 무시되어,
✅ 해결법: decltype(auto)
// C++14, 완벽 X, 정련 필요
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
authenticateUser();
return c[i];
}
Widget w;
const Widget& cw = w;
// auto 형식 연역: myWidget1 = Widget
auto myWidget1 = cw;
// decltype 형식 연역: myWidget2 = const Widget&
decltype(auto) myWidget2 = cw;
- 함수 반환 형식에 decltype 형식 연역을 작동하게 한다.
c[i]와return c[i]가 동일한 형식을 갖도록 만든다.
decltype(auto)지정자를 통해 해결할 수 있다.- 함수 반환 형식만이 아닌, 변수 선언에도 사용할 수 있다.
- 단, C++14부터 지원한다.
🔨 해결법 정련: 보편 참조 적용
deque<string> makeStringDeque(); // 팩토리 함수
// 정련 전
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);
// ⚠️ 에러 발생! 오른값 선언을 받을 수 없음!
auto s = authAndAccess(makeStringDeque(), 5);
Container& c는 왼값 참조이므로, 오른값 컨테이너는 전달할 수 없다.
// C++11, 정련 후
template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i)
-> decltype(std::forward<Container>(c)[i])
{
authenticateUser();
return std::forward<Container>(c)[i];
}
// C++14, 정련 후
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i) {
authenticateUser();
return std::forward<Container>(c)[i];
}
auto s = authAndAccess(makeStringDeque(), 5);
Container&& c로 보편 참조를 사용하도록 바꾼다.- 반환 값으로 값 전달 방식을 사용하고 있다.
- 일반적으로 좋은 방식은 아니지만, 표준 라이브러리(
string,vector, …)에서 색인 매개변수에 대해 값 전달 방식을 사용하므로, 여기선 이를 따른다.
- 일반적으로 좋은 방식은 아니지만, 표준 라이브러리(
⚠️ 복잡한 표현식의 경우
decltype(auto) f1() {
int x = 0;
// decltype(x) = int
return x;
}
decltype(auto) f2() {
int x = 0;
// decltype( (x) ) = int&
return (x);
}
- 이름이 아니며, 형식이
T인 왼값 표현식에 대해,decltype은 항상 왼값 참조(T&) 를 보고한다.
🧐 정리
decltype은 항상 변수나 표현식의 형식을 아무 수정 없이 보고한다.decltype은 이름이 아니며, 형식이T인 왼값 표현식에 대해, 항상T&형식을 보고한다.- C++14는
decltype(auto)를 지원한다.decltype(auto)는auto처럼 초기치로부터 형식을 연역하지만, 그 형식 연역 과정에서decltype의 규칙을 적용한다.
댓글남기기