[Modern C++] 항목 3: decltype의 작동 방식을 숙지하라

게시:     수정

카테고리:

태그: ,

이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 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의 규칙을 적용한다.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기