Programming | 프로그래밍 언어/EffectModernC++

템플릿 형식 연역 규칙

gyunnnnnn 2022. 10. 31. 15:32

템플릿 형식 연역

template<typename T>
void f(ParamType param);

f(expr);
  • 컴파일러는 expr을 이용해서 두 가지 형식을 연역한다.
    1. T
    2. ParamType
  • 두 형식이 다른 경우가 많다.
template<typename T>
void f(const T& param);

int x = 0;
f(x);
  • Tint로 연역되나 ParamTypeconst int&로 연역된다.
  • T에 대해 연역된 형식이 함수에 전달된 인수의 형식과 항상 같지는 않다.
  • T에 대해 연역된 형식은 expr의 형식, ParamType의 형태에 의존한다.
    • 형태에 따라 세가지 경우로 나뉜다.

경우 1: ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아닐 경우

형식 연역 과정

  1. expr이 참조 형식이면 참조 부분을 무시한다.
  2. expr의 형식을 ParamType에 대해 패턴 부합 방식으로 대응시켜서 T의 형식을 결정한다.
template<typename T>
void f(T& param);

int x = 27;
const int cx  = x;
const int& rx = x;

f(x);  // T는 int, param의 형식은 int&
f(cx); // T는 const int, param의 형식은 const int&
f(rx); // T는 const int, param의 형식은 const int&
  • 두번째, 세 번째 f 함수 호출에서 cxrxconst 값이 배정되었기 때문에 Tconst int로 연역된다.
    • 매개변수 param의 형식은 const int&가 되었다.
  • 세번째 f 함수 호출에서 rx의 형식이 참조지만 T는 비참조로 연역된다.
    • 연역 과정에서 rx의 참조성이 무시되었기 때문이다.

f의 매개변수의 형식을 T&에서 const T&로 바꾼다면?

template<typename T>
void f(const T& param);

int x = 27;
const int cx  = x;
const int& rx = x;

f(x);  // T는 int, param의 형식은 const int&
f(cx); // T는 int, param의 형식은 const int&
f(rx); // T는 int, param의 형식은 const int&
  • paramconst에 대한 참조로 간주되므로, constT의 일부로 연역될 필요가 없다.
  • 형식 연역 과정에서 rx의 참조성은 무시된다.

param이 참조가 아니라 포인터라면?

  • 형식 연역은 같은 방식으로 진행된다.
template<typename T>
void f(T* param);

int x = 27;
const int *px = &x;

f(&x);  // T는 int, param의 형식은 int *
f(&px); // T는 const int, param의 형식은 const int*

경우 2: ParamType이 보편 참조인 경우

  • 템플릿이 보편 참조 매개변수를 받는 경우에는 상황이 조금 불투명해진다.
  • 매개변수의 선언은 오른 값 참조와 같은 모습이지만 왼값 인수가 전달되면 오른값 참조와는 다른 방식으로 행동한다.
  • expr이 왼 값이면, TParamType 모두 왼값 참조로 연역된다.
    • 템플릿 형식 연역에서 T가 참조 형식으로 연역되는 경우는 이것이 유일하다.
    • ParamType의 선언 구문은 오른 값 참조와 같은 모습이지만, 연역된 형식은 왼값 참조이다.
  • expr이 오른값이면 정상적인(경우 1) 규칙들이 적용된다.
template<typename T>
void f((T&&) param);

int x = 27;
const int cx = x;
const int& rx = x;

f(x);  // x는 왼값.    T는 int&, param의 형식은 int&
f(cx); // cx는 왼값.   T는 const int&, param의 형식은 const int&
f(rx); // rx는 왼값.   T는 const int&, param의 형식은 const int&
f(27); // 27은 오른값. T는 int, param의 형식은 int&&
  • 보편 참조 매개변수에 관한 형식 연역 규칙들이 왼값 참조나 오른값 참조 매개변수들에 대한 규칙들과는 다르다.
  • 예들이 각각 해당 형식으로 연역되는 구체적인 이유는 보편 참조와 오른값 참조를 보면 알 수 있다.

경우 3: ParamType이 포인터도 아니고 참조도 아닌 경우

  • ParamType이 포인터도 아니고 참조도 아니라면, 인수가 함수에 값으로 전달되는 상황인 것이다.
  • param은 주어진 인수의 복사본으로 완전히 새로운 객체이다.
  • param이 새로운 객체라는 사실 때문에 expr에서 T가 연역되는 과정에서 아래와 같은 규칙들이 적용된다.
    1. expr의 형식이 참조이면, 참조 부분은 무시된다.
    2. expr의 참조 성을 무시한 후 exprconst이면 const 역시 무시한다.
    3. volatile일 경우에도 무시한다.
template<typename T>
void f(T param);

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
  • cxrxconst 값을 칭하지만 paramconst가 아니다.
  • paramcxrx의 복사본이므로 완전히 독립적인 객체이다.
  • 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와 param에 대해 연역되는 형식들은?
  • 배열 매개변수 선언이 포인터 매개변수처럼 취급되므로, 템플릿 함수에 값으로 전달되는 배열의 형식은 포인터 형식으로 연역된다.
  • 위 예에서 형식 매개변수 T는 배열이지만 const char*로 연역된다.

배열에 대한 참조로 선언한다면?

template<typename T>
void f(T& param);
f(name);
  • T에 연역된 형식은 배열의 실제 형식이 된다.
  • 형식은 배열의 크기를 포함한다.
  • 이 예에서 Tconst char [13]으로 연역되고 f의 매개변수의 형식은 const char(&)[13]으로 연역된다.

배열에 담긴 원소들의 개수를 연역하는 템플릿

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
  return N;
}

int keyVals[] = {1, 2, 3, 4, 5};
int mappedVals[arraySize(keyVals)]; // 원소 개수가 5개인 새 배열 선언 가능
  • 함수를 constexpr로 선언하면 함수 호출의 결과를 컴파일 도중에 사용할 수 있게 된다.
  • 위 템플릿을 사용하면 중괄호 초기화 구문으로 정의된 기존 배열과 같은 크기의 새 배열을 선언하는 것이 가능해진다.

함수 인수

  • 함수 형식도 함수 포인터로 붕괴할 수 있다.
  • 배열에 대한 형식 연역과 관련된 내용은 모두 함수에 대한 형식 영역에, 함수 포인터로의 붕괴에 적용된다.
void func(int, double); // 형식이 void(int, double)인 함수

template<typename T>
void f1(T param);       // 값 전달 방식
f1(func);               // param은 함수 포인터로 연역된다. 형식은 void(*)(int, double)

template<typename T>
void f2(T& param);      // 참조 전달 방식
f2(func);               // param은 함수 참조로 연역된다. 형식은 void(&)(int, double)

'Programming | 프로그래밍 언어 > EffectModernC++' 카테고리의 다른 글

auto의 형식 연역 규칙  (0) 2022.10.31