KoreanFoodie's Study

Effective Modern C++ | 항목 3 : decltype 의 작동 방식을 숙지하라 본문

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 26. 09:49

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

항목 3 : decltype 의 작동 방식을 숙지하라

핵심 :

1. decltype 은 항상 변수나 표현식의 형식을 아무 수정 없이 보고한다.
2. decltype 은 형식이 T 이고 이름이 아닌 왼값 표현식에 대해서는 항상 T& 형식을 보고한다.
3. C++14 는 decltype(auto) 를 지원한다. decltype(auto)는 auto 처럼 초기치로부터 형식을 연역하지만, 그 형식 연역 과정에서 decltype 의 규칙들을 적용한다.


컨테이너의 operator[ ] 반환 형식을 손쉽게 표현해 보자.

template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
->  decltype(c[i])
{
	authenticateUser();
	return c[i];
}

이름 앞에 auto 를 지정하는 것은 형식 연역과는 관련이 없다. 여기서의 auto 는 C++11 의 후행 반환 형식(trailing return type) 구문이 쓰인다는 점을 나타내는 것이다(반환 형식을 매개변수 목록 다음에서 정함).
C++14 부터는 이렇게 간단하게 만들 수도 있다.

template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
	authenticateUser();
	return c[i];
}

이 경우에는 반환 형식이 c[i] 로부터 연역된다. 그런데 이 경우, 다음과 같은 코드는 컴파일되지 않는다.

template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
	return c[i];
}


int main()
{
	std::deque<int> d{1,2,3,4,5,6};
	authAndAccess(d, 5) = 10;
}

왜냐하면, 형식을 연역하는 과정에서 Container 타입이 참조자가 제거되어 반환 형식은 int 가 되기 때문이다. 즉, 함수의 반환값으로서의 이 int 는 오른값이며, 결과적으로 위의 코드는 오른값 int 에 10을 배정하려 해서 컴파일이 되지 않는 것이다.

위 코드는 다음과 같이 고치면 된다. 함수의 반환 형식에 decltype 형식 연역이 적용되게 만드는 것으로, autoAndAccess 가 c[i] 의 반환 형식과 정확히 동일한 형식을 반환하게 만드는 것이다!

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
	return c[i];
}

decltype(auto) 는 다음과 같은 일반적인 상황에서도 유용하다.

class Widget {};

int main()
{
	Widget w;
	const Widget& cw = w;

	// auto 형식 연역 : Widget
	auto myWidget1 = cw;

	// decltype 형식 연역 : const Widget&
	decltype(auto) myWidget2 = cw;
}


만약 authAndAccess 가 왼값 참조 뿐만이 아니라 오른값 참조도 받을 수 있게 만드려면 어떻게 해야 할까? 간단한 해결책은, 다음과 같이 보편 참조를 활용하는 것이다.

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
	return c[i];
}

// 팩터리 함수
std::deque<std::string> makeStringDeque();

...

// 팩터리 함수가 만든 오른값 deque 의 다섯 번째 원소의 복사본을 만든다
auto s = autoAndAccess(makeStringDeque(), 5);

마지막으로, 항목 25 의 조언에 따라 보편 탐조에 std::forward 를 적용하면 끝이다.

// C++14 이상
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
	return std::forward<Container>(c)[i];
}

// C++11 버전
template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i)
 -> decltype(std::forward<Container>(c)[i])
{
	return std::forward<Container>(c)[i];
}


마지막으로, decltype 이 예상 밖의 결과를 제공하는 한 가지 경우를 보자.

decltype(auto) f1()
{
	int x = 0;
	// decltype(x) 는 int 이므로 int 를 반환
	return x;
}

// C++ 는 (x) 를 왼값으로 정의한다!
decltype(auto) f2()
{
	int x = 0;
	// decltype((x)) 는 int& 이므로 int& 를 반환
	return (x);
}

따라서, decltype(auto) 를 사용할 때는 주의해야 한다.

Comments