[Modern Effective C++] 1. 템플릿 타입 추론 규칙을 숙지
Updated:
template <typename T>
void f(ParamType param);
f(expr);
위와같은 템플릿 함수를 호출할때 컴파일러는 expr을 이용해 T 와 ParamType을 추론한다.
T에대해 추론된 형식은 항상 expr의 형식이 되지 않는다. expr의 형식외에도 ParamType의 형태에도 의존하기 때문. ParamType형태에 따라 세 가지 경우로 나뉜다.
-
ParamType이 포인터 또는 참조 형식이지만 Universal Reference는 아닌 경우
-
ParamType이 Universal Reference인 경우
-
ParamType이 포인터도 아니고 참조도 아닌 경우
템플릿 추론 방식
1. ParamType이 포인터 또는 참조 형식이지만 Universal Reference는 아닌 경우
template <typename T>
void f(T& param); // param은 참조 형식
int x = 27 // int
const int cx = x; // const int
const int& rx = x; // const int&
//위의 변수들로 f 호출결과
f(x); // T는 int, param은 int&
f(cx); // T는 const int, param은 const int&
f(rx); // T는 const int, param은 const int&
이를 통해 알수있는것은
- 객체의 const성은 T에 대해 추론된 타입의 일부가 된다.
- 타입 추론 과정에서 참조성은 무시가된다.
2. ParamType이 Universal Reference인 경우
템플릿이 Universal Reference 매개변수를 받는 경우에 매개변수의 선언은 오른값 참조와 같은 모습이지만, 왼값 인수가 전달되면 다른 방식으로 행동한다.
- expr이 왼값이면, T 와 ParamType 둘 다 왼값 참조로 추론된다.
- expr이 오른값이면, 1번과같이 ‘정상적인’ 규칙들이 적용
template <typename T>
void f(T&& param); // param은 Universal Reference
int x = 27; // 이전과 동일
const int cx = x // 이전과 동일
const int& rx = x // 이전과 동일
f(x); // x는 왼값 T와 param의 형식 모두 int&
f(cx); // cx는 왼값 T와 param의 형식 모두 const int&
f(rx); // rx는 왼값 T와 param의 형식 모두 const int&
f(27); // 27은 오른값 T는 int, param 타입은 int&&
Universal Reference가 관여하면 왼값 인수와 오른값 인수에 대해 서로 다른 타입추론이 적용된다.
3. ParamType이 포인터도 아니고 참조도 아닌 경우
인수가 값으로 전달되는 경우다.
template <typename T>
void f(T param);
param은 인수의 복사본으로 새로운 객체이다. 따라서 expr에서 T가 추론되는 과정에서 다음 규칙들이 적용된다.
- expr이 참조형식이면, 이전처럼 참조 부분은 무시된다.
- expr이 참조를 무시하고, expr이 const이면 그 const 역시 무시한다. volatile역시 무시된다.
int x = 27; // 이전과 동일
const int cx = x // 이전과 동일
const int& rx = x // 이전과 동일
f(x); // T와 param의 형식 모두 int
f(cx); // T와 param의 형식 모두 int
f(rx); // T와 param의 형식 모두 int
param은 cx나 rx의 복사본이므로, const가 아니게된다.
const char* const ptr = // ptr은 const 객체를 가리키는 const 포인터
"Fun with pointers"
f(ptr);
ptr은 다른 변수의 주소값을 가리키도록 변경할 수 없으며, 문자열 또한 변경이 불가능하다.
ptr을 f에 전달하면 포인터 자체는 값으로 전달된다. 그래서 값 전달 방식의 타입추론과 같은 규칙으로 적용된다.
결과적으로 param 타입은 const char*가 되어서 const 문자열을 가리키는 수정 가능한 포인터가 된다.
배열 인수
const char name[] = "J. P. Briggs" // name의 타입은 const char[13]
const char* ptrToName = name // 배열이 포인터로 붕괴된다.
template<typename T>
void f(T param); // 값 전달 매개변수가 있는 템플릿
f(name);
템플릿 함수에 값으로 전달되는 배열의 형식은 포인터로 추론된다. 즉 T는 const char*로 추론된다.
함수의 매개변수를 배열로 선언 할 수는 없지만, 배열에 대한 참조로 선언할 수는 있다.
template<typename T>
void f(T& param); // 참조 전달 매개변수가 있는 템플릿
f(name); // 배열을 f에 전달
param타입을 바꿔서 호출하면 T에 대해 추론된 형식은 배열의 실제 형식이된다.
이 예에서는 T는 const char [13]이 되고 param의 타입은 const char (&)[13]이 된다.
함수 인수
void someFunc(int, double); // someFunc는 함수;
// 형식은 void(int, double)
template<typename T>
void f1(T param); // f1의 param은 값 전달 방식
template<typename T>
void f1(T& param); // f2의 param은 값 전달 방식
f1(someFunc); // param은 함수 포인터로 추론됨
// 형식은 void(*)(int, double)
f2(someFunc); // param은 함수 참조로 추론됨
// 형식은 void (&)(int, double)
결론
- 템플릿 타입 추론 도중 참조 형식의 인수들은 참조성이 무시된다.
- Universal Reference에 대한 타입 추론 과정에서 왼값 인수들은 특별하게 취급된다.
- 값 전달 방식의 매개변수에 대한 타입 추론 과정에서 const 또는 volatile은 제거된다.
- 타입 추론 과정에서 배열이나 함수 이름에 해당하는 인수는 포인터로 붕괴한다.
단, 인수가 초기화하는 데 쓰이는 경우에는 포인터로 붕괴 하지 않는다.