KoreanFoodie's Study
Effective Modern C++ | 항목 1 : 템플릿 형식 연역 규칙을 숙지하라 본문
Effective Modern C++ | 항목 1 : 템플릿 형식 연역 규칙을 숙지하라
GoldGiver 2022. 10. 26. 09:48
C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!
항목 1 : 템플릿 형식 연역 규칙을 숙지하라
핵심 :
1. 템플릿 형식 연역 도중 참조 형식의 인수들은 비참조로 취급된다. 즉, 참조성이 무시된다.
2. 보편 참조 매개변수에 대한 형식 연역 과정에서 왼값 인수들은 특별하게 취급된다.
3. 값 전달 방식의 매개변수에 대한 형식 연역 과정에서 const 또는 volatile(또는 그 둘 다인) 인수는 비 const, 비 volatile 인수로 취급된다.
4. 템플릿 형식 연역 과정에서 배열이나 함수 이름에 해당하는 인수는 포인터로 붕괴한다. 단, 그런 인수가 참조를 초기화하는데 쓰이는 경우, 포인터로 붕괴하지 않는다.
C++ auto 는 템플릿에 대한 형식 연역을 기반으로 작동한다. 예시를 보자.
// 템플릿 선언의 형태
template<typename T>
void f(ParamType param) {}
...
// expr 로부터 T 와 ParamType 을 연역
f(expr);
// 예시코드
template<typename T>
void f(const T& param) {}
int main()
{
int x = 0;
// const int& 가 아닌 int 로 f 호출
f(x);
}
이는, T 에 대해 연역된 형식은 expr 의 형식뿐만 아니라 ParamType 형태에도 의존한다. 형식 연역 시나리오를 3 가지로 나누어 살펴보자.
- ParamType 이 포인터 또는 참조 형식이지만 보편 참조(universal reference) 는 아닌 경우
- ParamType 이 보편 참조인 경우
- ParamType 이 포인터도 아니고 참조도 아닌 경우
경우 1 : ParamType 이 포인터 또는 참조 형식이지만 보편 참조(universal reference) 는 아님
이 경우, 형식 연역은 다음과 같이 진행된다.
- 만일 expr 이 참조 형식이면 참조 부분을 무시
- 그 다음 expr 의 형식을 ParamType 에 대해 패턴 부합(pattern-matching) 방식으로 대응시켜 T 의 형식을 결정
template<typename T>
void f(T& param) {}
template<typename T>
void f2(const T& param) {}
int main()
{
int x = 27;
const int cx = x;
const int& rx = x;
// T 는 int, param 의 형식은 int&
f(x);
// T 는 const int, param 의 형식은 const int&
f(cx);
// T 는 const int, param 의 형식은 const int&
f(rx);
// T 는 int, param 의 형식은 const int&
f2(x);
// T 는 int, param 의 형식은 const int&
f2(cx);
// T 는 int, param 의 형식은 const int&
f2(rx);
}
위에서 T& 대신 T* 를 사용해도 타입은 같은 원리로 연역된다.
경우 2 : ParamType 이 보편 참조임
만일 expr 이 왼값이면, T 와 ParamType 둘 다 왼값 참조로 연역된다(매우 어색할 수 있다). 만약 expr 이 오른값이면, '정상적인'(경우 1의) 규칙들이 적용된다.
template<typename T>
void f(T&& param);
int main()
{
int x = 27;
const int cx = x;
const int& rx = x;
// x 는 왼값. 따라서 T 는 int&,
// param 의 형식 역시 int&
f(x);
// cx 는 왼값. 따라서 T 는 const int&,
// param 의 형식 역시 const int&
f(cx);
// rx 는 왼값. 따라서 T 는 const int&,
// param 의 형식 역시 const int&
f(rx);
// x 는 오른값. 따라서 T 는 int,
// param 의 형식은 int&&
f(27);
}
이 예들이 각각 해당 형식으로 연역되는 이유는 항목 24에서 후술하도록 한다.
경우 3 : ParamType 이 포인터도 아니고 참조도 아님
// param 이 값으로 전달
template<typename T>
void f(T param);
위의 경우, expr 형식의 참조성과 const 는 둘 다 무시된다. 심지어 volatile 도 무시된다!
template<typename T>
void f(T param);
int main()
{
int x = 27;
const int cx = x;
const int& rx = x;
// T 와 param 의 형식은 둘 다 int
f(x);
// T 와 param 의 형식은 둘 다 int
f(cx);
// T 와 param 의 형식은 둘 다 int
f(rx);
}
그럼 다음과 같은 경우는 어떨까?
template<typename T>
void f(T param);
...
const char* const ptr = "Fun";
f(ptr);
위 경우, 포인터 자체(ptr) 가 값으로 전달되어, param 은 const char* 의 형식을 가지게 된다. 즉, ptr 가 가리키는 것의 const 성은 보존되나, ptr 자체의 const 성은 사라지는 것이다.
배열 인수
초보 프로그래머는 흔히 배열이 배열의 첫 원소를 가리키는 포인터로 붕괴(decay) 하는 현상은, 템플릿에도 마찬가지로 적용된다.
template<typename T>
void f(T param) {}
int main()
{
const char name[] = "Tony";
const char* pName = name;
// T 는 const char[4] 가 아닌 const char* 로 연역됨
f(name);
}
아래와 같이 T 에 참조자를 달아 주면, f(name) 을 사용할 때 T 를 const char[4] 로 연역할 수 있다.
template<typename T>
void f(T& param) {}
이를 응용하면, 컴파일 타임 도중에 배열에 담긴 원소들의 갯수를 알아낼 수 있다! 물론 std::array 를 사용하는 것이 더 편하다.
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
...
int keyVals[] = {1, 2, 3, 4};
// 템플릿을 이용한 방식
int mappedVals[arraySize(keyVals)];
// 좀 더 모던한 방식
std::array<int, arraySize(keyVals)> mappedVals;
함수 인수
배열처럼, 함수 형식도 함수 포인터로 붕괴할 수 있다. 예시를 보자.
void someFunc(int, double);
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
// param 은 void (*)(int, double)
f1(someFunc);
// param 은 void (&)(int, double)
f2(someFunc);
'Tutorials > C++ : Advanced' 카테고리의 다른 글
Effective Modern C++ | 항목 3 : decltype 의 작동 방식을 숙지하라 (0) | 2022.10.26 |
---|---|
Effective Modern C++ | 항목 2 : auto 의 형식 연역 규칙을 숙지하라 (0) | 2022.10.26 |
Effective C++ | 항목 55 : Boo子有親! 부스트를 늘 여러분 가까이에 (0) | 2022.10.26 |
Effective C++ | 항목 54 : TR1 을 포함한 표준 라이브러리 구성요소와 편안한 친구가 되자 (0) | 2022.10.26 |
Effective C++ | 항목 53 : 컴파일러 경고를 지나치지 말자 (0) | 2022.10.26 |