본문 바로가기
프로그래밍/Modern Effective C++

Item 1. 템플릿 타입 추론

by drogrammer 2021. 8. 24.
반응형

C++ 템플릿이 타입을 어떻게 추론하는지 그 법칙을 알아보자.

1. 템플릿

일단, 템플릿이 뭐지? 라는 분을 위해 간략히 설명 하자면,
템플릿은 Generic 프로그래밍을 위해 C++ 이 제공하는 장치로, 변수 타입과 무관하게 클래스 및 함수를 정의 가능하게 해주는 방법이다. (음 뭔말이지?)
예를 들면, 아래와 같은 타입 별 교환 (swap) 함수를,

void swap(int& a, int& b) {
  int tmp = a;
  a = b;
  b = tmp;
}
void swap(double& a, double& b) {
  double tmp = a;
  a = b;
  b = tmp;
}
void swap(CustomType& a, CustomType& b) {
  CustomType tmp = a;
  a = b;
  b = tmp;
}


아래와 같은 템플릿 선언을 통해 타입과 무관하게 구현이 가능하다는 뜻이다.

template <typename T>
void swap(T& a, T& b) {
  T tmp;
  tmp = a;
  a = b;
  b = tmp;
}


결국, swap을 호출 시 사용한 인자에 따라 파라미터 타입을 추론하게 되는데,
예를 들어, 아래와 같이 호출하는 코드가 있으면,

int a = 1;
int b = 2;
swap(a, b);


파라미터 타입이 Int&로 추론되어 아래 함수가 선언/정의된다.

void swap(int& a, int& b) {
  int tmp = a;
  a = b;
  b = tmp;
}

 

2. 템플릿 타입 추론

지금부터 템플릿의 파라미터 타입이 어떻게 추론되는지 그 룰을 간략하게 정리해보자.
참고로, 본 포스팅에서 아래 포스팅의 type_name() 함수를 활용할 예정이니 참고하기 바란다.

 

Tip: 변수의 타입 출력하기

C++ 에서는 템플릿, auto 등을 통해 타입추론이 많이 사용되므로 실제 추론된 타입이 무엇인지 궁금할떄가 많다. 아래 스택오버플로우에서 관련 마음에 드는 방법을 찾아서 공유하고자 한다. (C++11

drogrammer.tistory.com

 

용어
파라미터 (Paramter) : 함수 선언 시 인자로 받을 변수를 의미  ex. int func(int a, int b)
인자 (Argument) : 함수 호출 시 실제로 파라미터로 사용하는 인자  ex. func(mya, myb)

 

2.1. 파라미터 타입이 레퍼런스일 경우

T& 이 파라미터 타입일 경우, 당연하게도 주어진 타입의 레퍼런스로 추론한다. ㅎ;; 단, 주어진 인자가 이미 레퍼런스일 경우에는 레퍼런스를 한번 더 붙이지 않는다. (즉, int 는 int& 으로 추론되고, int& 은 int&으로 추론된다.)

template <typename T>
void r(T& a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
int i = 1;
int& ir = i;
const int ci = 1;
const int& cir = ci;

r(i);    // int& 으로 추론
r(ir);   // int& 으로 추론
r(ci);   // int const& 으로 추론
r(cir);  // int const& 으로 추론

 

2.2. 파라미터 타입이 상수 레퍼런스일 경우

const T& 이 파라미터 타입일 경우, 2.1.1 과 방식은 동일하지만 추론된 결과에 const 가 붙는다. (당연한걸 왜 설명하고 있지..)

template <typename T>
void cr(const T& a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
int i = 1;
int& ir = i;
const int ci = 1;
const int& cir = ci;

cr(i);     // int const& 로 추론
cr(ir);    // int const& 로 추론
cr(cir);   // int const& 로 추론
cr(cir2);  // int const& 로 추론

 

2.3. 파라미터 타입이 포인터일 경우

T* 이 파라미터 타입일 경우, 포인터만 인자로 받을 수 있으므로 주어진 타입을 그대로 추론한다. (참고로, 레퍼런스는 일반타입과 레퍼런스 타입을 모두 받을 수 있었고 둘다 레퍼런스로 추론되었었다.)

template <typename T>
void p(T* a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
int i = 1;
const int* pi = &i;

p(&i);  // int* 로 추론
p(pi);  // int const* 로 추론

 

2.4. 파라미터 타입이 universal 레퍼런스일 경우

Universal 레퍼런스인 T&& 이 파라미터 타입일 경우,

  • 인자가 변수 같은 lvalue 면 2.1 과 동일하게 추론된다.
  • 인자가 리터럴, 식 등의 rvalue 면 universal 레퍼런스로 추론된다.
template <typename T>
void ur(T&& a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
int i = 1;
int& ir = i;
const int ci = 1;
const int& cir = ci;

ur(i);    // int& 로 추론 (lvalue)
ur(ir);   // int& 로 추론 (lvalue)
ur(ci);   // int const& 로 추론 (lvalue)
ur(cir);  // int const& 로 추론 (lvalue)

ur(10);      // int&& 로 추론 (rvalue)
ur(i + ir);  // int&& 로 추론 (rvalue)

 

2.5. 파라미터 타입이 value 타입일 경우

T 가 파라미터 타입일 경우, 인자의 레퍼런스, 상수(const), volatile 특성은 제거하고 추론한다.

template <typename T>
void v(T a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}

 

int i = 1;
int& ir = i;
const int ci = 1;
const int& cir = ci;

v(i);    // int 로 추론 (lvalue)
v(ir);   // int 로 추론 (lvalue)
v(ci);   // int 로 추론 (lvalue)
v(cir);  // int 로 추론 (lvalue)

v(10);      // int 로 추론 (rvalue)
v(i + ir);  // int 로 추론 (rvalue)

 

2.6. 그 외 특이한 케이스

2.6.1. 배열 (array) 추론

템플릿으로 전달되는 인자가 배열인 경우에는 value 추론 (T) 과 레퍼런스 추론 (T&) 결과가 다르다. Value 추론은 배열을 포인터로 추론해 내지만, 레퍼런스로 추론하면 고정크기 배열의 레퍼런스로 추론해 낸다.

template <typename T>
void v(T a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}

template <typename T>
void r(T& a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
int ia[] = {1, 2, 3, 4, 5, 6};

v(ia);  // int* 로 추론
r(ia);  // int [6]& 로 추론

 

2.6.2. 함수 포인터의 경우

템플릿으로 전달되는 인자가 함수일 경우에는 2.6.1. 배열 추론과 유사하게 value 추론 (T) 과 레퍼런스 추론 (T&) 결과가 다르다. Value 추론은 함수 포인터로 추론해 내지만, 레퍼런스 추론은 함수 레퍼런스로 추론해 낸다.

template <typename T>
void v(T a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}

template <typename T>
void r(T& a) {
  std::cout << type_name<decltype(a)>() << std::endl;
}
// 추론 대상 함수
void func(int, double) {}
v(func);  // void (*)(int, double) 로 추론
r(func);  // void (int, double)& 로 추론
반응형

댓글