형식 템플릿 매개변수( type template parameter )의 추론
이 글은 다음의 템플릿 함수가 호출되었을 때, 컴파일러에 의해서 어떤 함수로 구체화되는지에 관한 내용을 설명합니다.
바로 본론으로 들어가는 것이 좋을 때도 있지만, 지금처럼 약간 구체적이지 않은 것에 관하여 말하려면, 용어를 정리하고 시작하는 것이 매우 편리합니다.
template< typename T >
void func( ParamType param);
func( expr ); // template 함수 호출
먼저, 위의 T는 형식 템플릿 매개변수( type template parameter )라고 합니다.
간략하게는 템플릿 형식( template type )이라고도 합니다.
그리고, ParamType은 템플릿 함수의 매개변수 타입입니다.
이 타입은 형식 템플릿 매개변수 T와 같은 때도 있지만 대체로 다릅니다.
여기서는 혼동이 없도록 위에 적은 대로 "ParamType"으로 쓰겠습니다.
마지막으로, expr은 함수를 실제 호출할 때 사용될 표현식입니다.
( expr은 expression의 약자 )
컴파일러는 형식 템플릿 매개변수를 expr과 ParamType을 통하여 추론하고, 이 추론된 타입을 가진 구체적인 함수를 컴파일 시( 좀 더 정확하게 말하면, 함수가 호출될 때 )에 작성합니다.
그런데, 이 구체화된 함수는 직접 볼 수 없으므로, 함수의 형태를 머릿속에서 예상하는 수밖에 없습니다.
그러므로, 컴파일러의 추론 규칙을 정확하게 이해하고 템플릿 함수를 작성하는 것은 의미가 있습니다.
컴파일러가 템플릿 형식을 추론하는 규칙은 템플릿의 형태에 따라 세 가지로 나눌 수 있습니다.
1) ParamType이 참조 형식이지만 보편 참조는 아닐 경우
이런 경우의 템플릿은 다음과 같은 형태를 갖고 있습니다.
template< typename T >
void func( T& param){ };
여기서, ParamType은 T&이 됩니다.
따라서, expr의 타입이 참조 형식인 경우, 참조 속성을 무시하고( ParamType이 이미 참조 타입이므로 ), 타입의 나머지 부분만으로 템플릿 타입 T를 추론합니다.
예를 들어, 다음과 같은 변수들이 있다고 합시다.
int x = 27;
const int cx = x;
const int& rx = x;
그럼, ParamType과 템플릿 타입 T는 다음과 같이 추론됩니다.
func(x); // ParamType은 int&, T는 int
func(cx); // ParamType은 const int&, T는 const int
func(rx); // ParamType은 const int&, T는 const int
func( x )를 호출하면, 컴파일러는 x의 타입( int )과 템플릿 함수 선언을 보고, ParamType을 int& 으로 추론하고, T는 int로 추론합니다.
func( cx )를 호출하면, cx는 const 속성을 갖고 있으므로, ParamType도 const 속성이 있다고 추론합니다.
따라서, ParamType은 const int&으로 추론되고, 이로부터 T는 const int로 추론됩니다.
그리고, func( rx )를 호출하면 rx의 참조 속성은 무시하므로, 추론 결과는 func(cx)를 호출할 때와 같습니다.
만약, 템플릿 함수의 정의가 다음과 같다면, 위 표현식을 사용한 템플릿 타입의 추론 결과는 다음과 같이 변경됩니다.
template< typename T >
void func( const T& param ){};
int x = 27; // 위와 같음
const cx = x;
const rx = x;
func(x); // ParamType은 const int&, T는 int
func(cx); // ParamType은 const int&, T는 int
func(rx); // ParamType은 const int&, T는 int
ParamType에 이미 const 속성이 있으므로, 템플릿 타입 T는 int로 추론됩니다.
2) ParamType이 보편 참조( universal reference )인 경우
이런 경우의 템플릿은 다음과 같은 형태를 갖고 있습니다.
template< typename T >
void func( T&& param ); // 보편 참조
위와 같이, 형식 템플릿 매개변수가 T일 때, T&& 형태의 타입을 보편 참조( universal reference )라고 부릅니다.
이 참조 형식은 왼값( l-value )과 오른값( r-value )을 하나의 템플릿으로 모두 받아서 처리하기 위한 타입으로, 오른값 참조( r-value reference )와는 다릅니다.
이 둘을 구분하기 위해서는 타입의 추론이 개입되어 있는 여부를 확인하면 됩니다.
만일, 위와 같이 매개변수 타입을 추론할 필요가 있는 경우의 ParamType이 보편 참조입니다.
아래와 같이 그렇지 않은 경우, ParamType은 오른값 참조입니다.
template < typename T >
void func( Widjet&& w ){ }; // 오른값 참조
Widjet w1;
func( w1 ); // error !
여기서, Widjet은 설명을 위한 임의의 사용자 객체입니다.
그리고, 이러한 오른값 참조를 매개변수로 하는 함수는 왼값으로 호출할 수 없습니다.
만약, 표현식 expr이 왼값이면, ParamType과 T 모두 왼값으로 추론됩니다.
반대로, expr이 오른값이면 이전 항목 1)의 규칙이 적용됩니다.
( 오른값 참조는 참조 타입의 하나입니다. )
예를 들어, 다음과 같은 변수들이 있다고 합시다.
int x = 27;
const int cx = x;
const int& rx = x;
그럼, ParamType과 템플릿 타입 T는 다음과 같이 추론됩니다.
func(x); // ParamType은 int&, T는 int&
func(cx); // ParamType은 const int&, T는 const int&
func(rx); // ParamType은 const int&, T는 const int&
func(27); // ParamType은 int&&, T는 int
위에서, x, cx, rx 모두 왼값이므로, ParamType과 T는 이 변수들 타입의 왼값 참조가 됩니다.
그리고, 27은 우측값이므로, ParamType은 우측값 참조가 됩니다.
3) ParamType이 참조 형식이 아닌 경우
이런 경우의 템플릿은 다음과 같은 형태를 갖고 있습니다.
template< typename T >
void func( T param );
이 경우는 ParamType이 참조가 아니므로, 함수의 인수가 값으로 전달되는 상황이 됩니다.
따라서, param은 인수의 복사본이므로, 인수의 상수와 참조 속성은 무시됩니다.
또한 volatile 속성이 있다면 그것도 무시합니다.
이전 항목과 같이, 다음과 같은 변수들이 있다고 합시다.
int x = 27;
const int cx = x;
const int& rx = x;
그럼, ParamType과 템플릿 타입 T는 다음과 같이 추론됩니다.
func(x); // ParamType은 int, T는 int
func(cx); // ParamType은 int, T는 int
func(rx); // ParamType은 int, T는 int
매개변수는 인수의 복사본으로, 인수가 상수라고 해서, 매개변수가 상수일 필요가 없습니다.
매개변수와 인수는 별개의 객체라고 할 수 있습니다.
예를 하나 더 들어보겠습니다.
const char* pStr = "pointer for C-style string";
const char* const pcStr = "const pointer for C-style string";
func(pstr); // ParamType은 const char*, T도 const char*
func(pcstr); // 위와 같음
func의 인수인 pStr는 C-style string을 가리키는 포인터입니다.
주의할 점은, pStr 변수가 복사되는 것이지, pStr이 가리키는 문자열이 복사되는 것이 아니라는 것입니다.
따라서, pStr 변수가 가리키는 문자열의 상수 속성을 무시하는 것이 아닙니다.
그래서, 템플릿 타입은 const char*로 추론됩니다.
참고로, C-style string에 관한 내용은 여기에서 볼 수 있습니다.
그리고, pcStr를 인수로 함수를 호출할 때, 위와 마찬가지로 상수 포인터인 pcStr의 const 속성만 없어지므로, * 기호 오른쪽의 const 속성이 무시됩니다.
그래서, 여기도 템플릿 타입은 const char*로 추론됩니다.
참고로, const 포인터는 그 포인터가 가리키는 대상을 변경할 수 없는 포인터를 말합니다.
이 글과 관련된 글들
auto의 형식 추론( type deduction ) 규칙
'C, C++ > C++ 언어' 카테고리의 다른 글
[C++] auto의 형식 추론( type deduction ) 규칙 (0) | 2024.09.13 |
---|---|
[C++] 완벽한 전달( perfect forwarding )에 대한 설명 (1) | 2024.09.08 |
[C++] 범위 있는( scoped ) enum에 관하여 (0) | 2024.09.05 |
[C++] 컴파일 시 사용 여부를 알려주는 constexpr 키워드 (2) | 2024.09.05 |
[C++] 재정의( overriding )와 override 키워드 (0) | 2024.09.01 |