C, C++/C++ 언어 / / 2024. 10. 13.

[C++] 함수 템플릿( function template )에 대한 설명 2

여러개의 형식 템플릿 매개변수를 가진 함수 템플릿

 

이 글은 "함수 템플릿( function template )에 대한 설명 1"에 연결되는 글입니다.

 

[C++] 함수 템플릿 ( function template )에 대한 설명 1

함수 템플릿 용도와 정의함수를 작성하다 보면, 매개변수의 타입만 다르고, 함수의 내용 자체는 중복되는 경우가 있습니다.다음 예가 그러한 함수의 하나일 것입니다.int add ( int a, int b){ return a +

codingembers.tistory.com

 

이전 글에서 add 함수 템플릿을 정의했습니다.

template < typename T >	// 함수 템플릿
T add ( T a, T b){
    return a + b;
}

 

그리고,   add( 2, 5 )   를 호출하면, 컴파일러은 이 함수 템플릿을 사용해, 다음과 같은 함수를 생성하고 호출할 것입니다.

template<>
int add ( int a, int b){
    return a + b;
}

 

그런데, 만약,   add( 2, 3.14 )   함수를 호출하면, 이 함수 템플릿은 어떤 함수를 생성할까요?

아마 가장 가능성 있는 예상은 다음의 템플릿 함수일 것입니다.

template<>  
double add( double a, double b){
    return a + b;
}

double 타입이 인수   2    3.14   를 모두 담을 수 있기 때문입니다.

그렇지만, 놀랍게도   add( 2, 3.14 )   의 함수 호출은 오류를 발생시킵니다.

template < typename T >
T add ( T a, T b){
    return a + b;
}
...

int ret = add( 2, 3.14);	// error !!

▼출력

error: no matching function for call to 'add(int, double)'

이것은 컴파일러가 함수의 인자들로부터 적절한 타입을 추론하지 못해서, 템플릿 함수를 구체화( instantiation )하지 못했기 때문에 발생한 오류입니다.

위에서 했던 예상은, "컴파일러가 값   2   의 타입인 int를 암시적으로 double 타입으로 변환하고, 이 double 타입의 인수를 처리할 수 있는 템플릿 함수를 생성할 것이다"라는 것이었을 겁니다.

그러나, 컴파일러는 실제 구현된 함수를 보기 전까지는, 주어지는 인수를 다른 타입으로 변환할 수 없습니다.

따라서, int를 double를 변환할 수 없고, 그 결과 형식 템플릿 매개 변수를 추론할 수 없었던 것입니다.

( int와 double 둘 중의 하나로 추론해야 되는데, 아무런 힌트도 없습니다. )

 

이 문제를 해결할려면, 우선 함수 인수의 타입이 일치해야 한다는 것을 알았습니다.

따라서, 다음과 같이 하면 원하는 값을 구할 수 있습니다.

int ret = add( static_cast<double>(2), 3.14);	// ok

그러나, 이러한 형태의 코드는 너무 불만족스럽습니다.

 

다른 방식으로는, 명시적으로 형식 템플릿 매개변수를 지정하는 방법이 있습니다.

int ret = add< double >( 2, 3.14);  // 명시적인 템플릿 인수 지정

이렇게 템플릿 인수( template argument )를 지정해서, 함수 템플릿을 호출하면, 컴파일러는 double 타입의 템플릿의 함수를 작성할 것입니다.

이제, 함수가 존재하므로, 컴파일러는 인수   2   의 타입을 암시적으로 double 타입으로 변환합니다.

그리고, 성공적인 함수 호출을 하게 될 것입니다.

 

그런데, 이렇게 항상 템플릿 인수를 지정하기 위해, 인수의 타입에 신경을 써야 하는 것이 맘에 걸립니다.

그리고, 애초에 이러한 문제가 발생한 원인은 서로 다른 인수의 타입을 하나( 여기서는   T   )로 통일해야 한다는 전제 때문입니다.

 

만약, 이러한 전제를 무시한다면, 서로 다른 타입의 인수를 함수에 전달해, 함수 내에서 처리하는 방법을 생각할 수 있습니다.

그리고, 이러한 사고 방식이 합리적입니다.

다음의 예문이 이렇게 문제를 푸는 방법입니다.

// 두 개의 타입을 처리하는 함수 템플릿
template< typename T, typename U >	
T add( T a, U b){
    return a + b;
}

int main(){

    double ret = add( 2, 3.14 ); // 인수의 타입에 자유로운 함수 호출
    cout << "ret: " << ret << endl;
}

▼출력

ret: 5

위의 함수 템플릿은 서로 다른 두 개의 타입을 받는 템플릿입니다.

컴파일러는 인수   2   로부터 int 타입을,   3.14   로부터는 double 타입을 추론합니다.

그리고, 다음과 같은 함수를 구체화합니다.

template<>
int add( int a, double b){
    return a + b;
}

그런데, 아직 해결해야 할 문제가 남아 있습니다.

형식 템플릿 매개변수   T   가 int 타입으로 대체되기 때문에, 함수의 반환 값이 int 타입이 된다는 것입니다.

그래서, 결과가   5.14   가 되어야 되는데,   5   가 되었습니다.

 

그리고, ' 그럼   3.14   를 먼저 전달하면 되지 않을까'라고 생각하는 것은 전혀 문제가 해결되지 않습니다.

이런 방법은 위에서 제시했던 방법들보다 더 나쁜 방법입니다.

 

그럼, 어떻게 반환 값의 타입을 결정해야 할까요?

이럴때는, 컴파일러가 매개 변수의 타입을 추론하는 것과 같이, 이것조차 컴파일러가 결정하라고 떠넘기는 것입니다.

template< typename T, typename U >
auto add( T a, U b){
    return a + b;
}

이제, 컴파일러는 함수 반환 값의 타입을 double로 추론할 것입니다.
이것은   a   또는   b   가 double 타입으로 형 변환되고, 변환 후의   a+b   의 계산 값은 double 타입일 것이기 때문입니다.

// 함수 템플릿이 구체화한 함수 ( 눈에는 보이지 않음 )
template <>
double add( int a, double b ){
    return a + b;
}

int main(){
    double ret = add( 2, 3.14 ); 
    cout << "ret: " << ret << endl;
}

▼출력

ret: 5.14

 

함수 템플릿의 오버로딩( overloading )

함수 템플릿도 일반 함수와 같이 오버로딩 할 수 있습니다.

즉, 함수 템플릿의 이름이 동일하더라도 함수 매개변수의 수와 타입이 다르면, 중복된 정의로 보지 않는다는 것입니다.

template < typename T >
T add ( T a, T b){
    cout << "one type template parameter\n";
    return a + b;
}

template< typename T, typename U >
auto add( T a, U b ){
    cout << "two type template parameters\n";
    return a + b;
}

 

그리고, 이러한 함수 템플릿이 있는 경우, 이전 항목에서 설명한 것과 같이,   add( 2, 3.14 )   함수 호출은 위에서 두 번째의 함수 템플릿을 통해 만들어지는 함수를 호출할 것입니다.

 

그럼,   add( 3, 5 )   를 호출하면, 컴파일러는 위의 둘 중에서 어떤 함수 템플릿을 이용할까요?

int main(){

    add( 2, 3.14 );	// add<T, U> 함수 템플릿 이용
    add( 3, 5 );	// ??
}

▼출력

two type template parameters
one type template parameter

main 함수의 마지막 함수 호출   add( 3, 5 )   는 위 두 개의 함수 템플릿 모두로부터 함수를 만들고, 호출할 수 있습니다.

그렇지만, 이런 경우 컴파일러는 적은 수의 매개변수를 가진 첫 번째 함수 템플릿을 선호합니다.

template<>	// 첫 번째 함수 템플릿으로부터 구체화된 함수
int add ( int a, int b){
    cout << "one type template parameter\n";
    return a + b;
}

이전 글에서 템플릿 함수보다 일반 함수를 선호했던 것과 마찬가지로, 컴파일러는 타입의 선택이 제한적인 템플릿 함수를 선호합니다.

그 결과 다음 같은 출력을 하게 되는 것입니다.

one type template parameter

.

이 글과 관련있는 글들

클래스 템플릿( class template )에 대한 설명

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유