[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은 제거된다.
  • 타입 추론 과정에서 배열이나 함수 이름에 해당하는 인수는 포인터로 붕괴한다.
    단, 인수가 초기화하는 데 쓰이는 경우에는 포인터로 붕괴 하지 않는다.