Effective Modern C++ Item 3 내용 정리
Chap 1. 형식 연역 (Type Deduction)
Item 3. decltype (declared type)
📌 Main Point
decltype
은 항상 변수나 표현식의 형식을 아무 수정 없이 보고함
decltype
은 형식이 T
이고 이름이 아닌 왼값 표현식에 대해서는 항상 T&
형식을 보고함
- C++14는
decltype(auto)
를 지원함
decltype(auto)
는 auto
처럼 초기치로부터 형식을 연역하지만, 그 형식 연역 과정에서 decltype
의 규칙들을 적용
💡 decltype 이란
📌 Main Point
decltype
은 항상 변수나 표현식의 형식을 아무 수정 없이 보고함decltype
은 형식이T
이고 이름이 아닌 왼값 표현식에 대해서는 항상T&
형식을 보고함- C++14는
decltype(auto)
를 지원함decltype(auto)
는auto
처럼 초기치로부터 형식을 연역하지만, 그 형식 연역 과정에서decltype
의 규칙들을 적용
✍ Ex) 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&
👉 위 코드에서 decltype
은 예상한대로의 형식을 알려줌
💡 decltype을 이용한 반환 형식
- C++11에서
decltype
은 함수의 반환 형식이 그 매개변수 형식들에 의존하는 함수 템플릿을 선언할 때 주로 사용됨 - 예를 들어 컨테이너 하나와 색인 하나를 받고, 대괄호 색인화를 통해서 (=
컨테이너[색인]
구문을 이용해서) 컨테이너의 한 요소를 돌려주는 함수를 작성할 때, 함수의 반환 형식은 반드시 색인화 연산의 반환 형식과 동일해야 함 - 대체로 형식
T
의 객체들을 담은 컨테이너에 대한operator[]
연산은T&
를 돌려줌 (e.g.std::deque
,std::vector
) - 하지만
std::vector<bool>
에 대한operator[]
는bool&
이 아니라 완전히 새로운 객체를 돌려줌 (자세한 내용은 Item 6에서 설명) - 여기서 중요한 점은, 컨테이너의
operator[]
의 반환 형식이 컨테이너에 따라 다를 수 있음 decltype
을 이용하면 그런 함수의 반환 형식을 손쉽게 표현할 수 있음
✍ Ex) decltype을 이용해서 반환 형식을 계산하는 방법
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) // C++11
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
🔑 함수 이름 앞에 auto
를 지정하는 것은 형식 연역과는 아무런 관련이 없음
🔑 함수 이름 앞의 auto
는 C++11의 후행 반환 형식(trailing return type) 구문이 쓰인다는 점을 (즉, 함수의 반환 형식을 이 위치가 아니라 매개변수 목록 다음에 = "->
" 다음 위치에) 선언하겠다는 점을 나타냄
👉 후행 반환 형식 구문에는 반환 형식을 매개변수들을 이용해서 지정할 수 있다는 장점이 있음
⛔ 위 코드에서 일반적인 방식으로 함수 이름 앞에서 반환 형식을 지정한다면, c
와 i
는 사용할 수 없음 (둘 다 아직 선언되지 않았기 때문)
🔑 C++11은 람다 함수가 한 문장으로 이루어져 있다면 그 반환 형식의 연역을 허용
🔑 C++14는 허용 범위를 더욱 확장해서 모든 람다와 모든 함수의 반환 형식 연역을 허용 (return 문이 여러 개인 함수도 허용 - 단, 모든 return 문의 형식 연역 결과가 일치해야 함)
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) // C++14
{
authenticateUser();
return c[i]; // 반환 형식은 c[i]로부터 연역됨
}
👉 authAndAccess
의 경우 C++14에서 후행 반환 형식을 생략하고 함수 이름 앞의 auto
만 남겨 두어도 됨
👉 이러한 형태의 선언에서는 실제로 auto
가 형식 연역이 일어남을 뜻하는 용도로 쓰임 (auto
는 컴파일러가 함수의 구현으로부터 함수의 반환 형식을 연역할 것임을 뜻함)
🔑 Item 2에서 설명했듯이, 함수의 반환 형식에 auto
가 지정되어 있으면 컴파일러는 템플릿 형식 연역을 적용 (T
객체들을 담은 컨테이너에 대한 operator[]
연산은 대부분의 경우에는 T&
를 돌려줌)
⛔ Item 1에서 설명했듯이, 템플릿 형식 연역 과정에서 초기화 표현식의 참조성이 무시된다는 점이 문제임
std::deque<int> d;
…
authAndAccess(d, 5) = 10; // 사용자를 인증하고, d[5]를 돌려주고,
// 그런 다음 10을 d[5]에 배정함
// 이 코드는 컴파일 되지 않음!
👉 위 코드에서 d[5]
는 int&
를 돌려주나, authAndAccess
에 대한 auto 반환 형식 연역 과정에서 참조가 제거되기 때문에 결국 반환 형식은 int
가 됨
👉 함수의 반환값으로서의 이 int
는 하나의 오른값이며, 결과적으로 위 코드는 오른값 int
에 10
을 배정하려 함 (이 것은 C++에서 금지되어 있기 때문에, 코드는 컴파일되지 않음)
💡 decltype(auto)을 이용한 반환 형식
authAndAccess
가 원하는 대로 작동하려면, 함수의 반환 형식에 decltype 형식 연역이 적용되게 만들어야 함 (authAndAccess
가c[i]
의 반환 형식과 정확히 동일한 형식을 반환하게 만들어야 함)- C++14의
decltype(auto)
지정자를 통해서 형식 추론이 일어나는 일부 경우에서 decltype 형식 연역 규칙들이 적용되도록 함 (auto
는 해당 형식이 연역되어야 함을,decltype
은 그 연역 과정에서 decltype 형식 연역 규칙들이 적용되어야 함을 뜻함)
✍ Ex) decltype(auto)을 이용해서 반환 형식을 계산하는 방법
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container& c, Index i) // C++14
{
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&
✍ Ex) authAndAccess의 정련 방법
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);
👉 컨테이너 c
는 비const 객체에 대한 왼값 참조로서 함수에 전달됨 → 함수가 돌려준 컨테이너 요소를 클라이언트가 수정할 수 있게 하기 위한 것임 (이로 인해 함수에 오른값 컨테이너는 전달할 수 없다는 것이 문제)
std::deque<std::string> makeStringDeque(); // 팩터리 함수
// makeStringDeque가 돌려준 deque의
// 5번째 원소의 복사본을 만듦
auto s = authAndAccess(makeStringDeque(), 5);
👉 이런 용법을 지원하려면 authAndAccess
가 왼값뿐만 아니라 오른값도 받아들이도록 선언을 고쳐야 함
⛔ 왼값 참조 매개변수를 받는 버전, 오른값 참조 매개변수를 받는 버전을 따로 만들어서 사용할 수도 있지만 이것은 별로임
🔑 왼값과 오른값 모두에 묶일 수 있는 참조 매개변수를 authAndAccess
에 도입해서 위의 방법을 피함 (Item 24에서 다루는 보편 참조가 이러한 용도에 사용됨)
// 보편 참조 매개변수를 받도록 authAndAccess 선언 수정
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i); // c가 보편 참조
// 수정된 선언에 맞게 템플릿의 구현도 수정해야 함 (Item 25에서 다룰 std::forward 적용)
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container&& c, Index i) // C++14 최종 버전
{
authenticateUser();
return std::forward<Container>(c)[i];
}
🔑 위의 코드를 사용하려면 C++14 컴파일러가 필요하기 때문에, C++11 버전에서는 반환 형식을 명시적으로 지정하는 것으로 구현 부분을 수정
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&
를 보고함) - 대부분의 왼값 표현식에는 왼값 참조가 포함되어 있기 때문에 이 점으로 인해 달라지는 경우는 드물지만 간혹 있음
✍ Ex) 왼값 표현식에 따른 decltype 차이
int x = 0;
👉 위 코드에서 x
는 변수의 이름이기 때문에 decltype(x)
는 int
임
👉 x
를 괄호로 감싸서 (x)
를 만들면 이름보다 복잡한 표현식이 됨 (이름으로서의 x
는 하나의 왼값이며, C++은 (x)
라는 표현식도 왼값으로 정의함)
👉 따라서 decltype((x))
는 int&
임
⛔ C++11에서는 이러한 현상이 큰 문제를 일으키진 않지만, decltype(auto)
를 지원하는 C++14에서는 return 문 작성 방식에 따라 함수의 반환 형식 연역 결과가 달라질 수 있음
decltype(auto) f1()
{
int x = 0;
…
return x; // decltype(x)는 int → f1은 int를 반환
}
decltype(auto) f2()
{
int x = 0;
…
return (x); // decltype((x))는 int& → f2은 int&를 반환
}
⛔ f2
가 f1
과는 다른 형식을 돌려준다는 점뿐만 아니라, 자신의 지역 변수에 대한 참조를 돌려준다는 점도 주의해야 함
Author And Source
이 문제에 관하여(Effective Modern C++ Item 3 내용 정리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@minsukim/EMCPP-Item03저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)