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

[C++] 비-형식 템플릿 매개변수( non-type template parameter)

비-형식 템플릿 매개변수에 대한 설명

이 글은 이전의 템플릿에 관한 글들과 연결되어 있습니다.

시작하기 전에 '함수 템플릿 ( function template )에 대한 설명 1'을 읽어 보길 바랍니다.

 

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

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

codingembers.tistory.com

 

템플릿의 정의에서 형식 템플릿 매개변수( type template parameter )가 임의의 타입이 들어갈 위치를 표시한다면, 비-형식 템플릿 매개변수컴파일 시 값을 알 수 있는 표현식으로 대체될 자리를 표시( placeholder )하는, 고정된 타입의 매개변수입니다.

#include <iostream>
#include <cmath>	// for std::pow
using namespace std;

template< int N >   // 비형식 템플릿 매개변수
int pow2(){
    int val = std::pow(N, 2);
    return val;
}

int main(){

    pow2<3>;	// ok
    pow2<5>;	// ok
    
    int rv = 5;	// 실시간 변수
    pow2<rv>;	// error!

    constexpr int n = 5;    // 컴파일 시 상수
    int val = pow2<n>();    // ok
    cout << "double-powerd value: " << val << endl;
}

위에서 함수 템플릿에 선언된 N이 비-형식 템플릿 매개변수입니다.

그리고, 이러한 매개변수를 가진 템플릿으로부터 함수를 생성하려면, 각괄호   <>   안에 임의의 데이터의 타입을 선언하는 것이 아니라, 고정된 타입( 여기서는 int )의 값을 나타내는 표현식을 사용해야 합니다.

 

위 예문에서와 같이, 컴파일러가   pow2<3>   선언을 만나게 되면, 함수 템플릿으로부터 다음과 같은 함수를 구체화( instantiation )합니다.

template<>	// 함수 템플릿으로부터 구체화되었음
int pow2<3>(){
    int val = std::pow(3,2);	// val의 값은 9
    return val;
}

이 구체화 과정에서, 컴파일러는   N   으로 표시되어 있는 기호를 표현식의 값   3   으로 대체하는 것입니다.

그리고, 프로그램 실행 시, 이렇게 만들어진 함수   pow2<3>()   를 호출하게 됩니다.

 

마찬가지로,   pow2<5>   는 다음과 같이 구체화됩니다.

template<>	
int pow2<5>(){	// 함수의 이름은 pow2<5>
    int val = std::pow(5,2);	
    return val;
}

여기서 주목할 것은, 템플릿 함수를 선언 시, 비-형식 템플릿 인수가 달라지면 그에 따라 생성되는 템플릿 함수의 이름도 달라진다는 것입니다.

즉,   pow2<3>()     pow2<5>()   는 형태만 같은, 별개의 함수입니다.

그러기 때문에, 당연히 함수 중복 오류도 발생하지 않습니다.

 

하지만, 다음과 같은 문장은 오류입니다.

int rv = 5;	// 실시간 표현식
pow2<rv>;	// error !

템플릿 함수를 생성하는 시기가 컴파일을 하는 때라는 것을 알고 있을 겁니다.

하지만, 위의 변수   rv   의 값은 실행 시가 되어서야 알 수 있습니다. ( 실행 시에 변수가 메모리에 생성됩니다. )

그러므로, 컴파일러는 이 표현식으로는 템플릿 함수를 생성할 수 없습니다.

 

이런 이유로, 비-형식 템플릿 매개변수가 사용된 위치를 대체할 표현식은, 컴파일 시에 그 값을 알 수 있는 표현식이어야 하고, 이 표현식을 상수 표현식( const expression )이라고 합니다.

그리고, 이러한 상수 표현식은 컴파일 시 상수와 컴파일 시 값을 알 수 있는 함수들로 구성됩니다.

constexpr int n = 5;    // 컴파일 시 상수
int val = pow2<n>();
cout << "doubled value: " << val << endl;

▼출력

doubled value: 25

위 예문에서, 컴파일 시 상수인 constexpr 변수   n   이 상수 표현식의 예입니다.

컴파일러는 컴파일 시,    n   의 값이 5라는 것을 알고 있으므로,   pow2<n>   선언을 만나게 되면,   pow2<5>   함수를 생성하게 될 것입니다. 

 

상수 표현식을 나타내는 constexpr 키워드에 관한 내용은 여기서 볼 수 있습니다.

 

[C++] 컴파일 시 사용 여부를 알려주는 constexpr 키워드

constexpr 변수constexpr 변수는 컴파일 시에 알 수 있는 값을 가진 상수임을 선언한 변수입니다.이 변수는 const 속성이 있으므로, 선언 시 반드시 초기화를 해야 합니다.constexpr int count = 0; 이러한 변

codingembers.tistory.com

 

클래스 템플릿에서의 비-형식 템플릿 매개변수

함수 템플릿뿐만 아니라, 클래스 템플릿에서도 비-형식 템플릿 매개변수를 사용할 수 있습니다.

std::array가 대표적인 예입니다.

이 클래스 템플릿의 템플릿 매개변수 선언( template parameter declaration )을 보면 다음과 같이 정의되어 있습니다.

// std::array에서 발췌
template<typename _Tp, std::size_t _Nm>

이 array 클래스 템플릿이 C 타입의 배열과 동일한 기능을 수행하기 위해선, 컴파일 시 원소의 타입과 개수를 알아야 합니다.

그렇게 하기 위해선, 형식 템플릿 매개변수뿐만 아니라, 비-형식 템플릿 매개변수를 사용해서 컴파일 시 원소를 개수를 컴파일러에게 알려줘야 합니다.

( 함수의 매개변수는 상수 표현식이 될 수 없습니다. 따라서 컴파일 시에 함수를 통해서 원소의 개수를 전달할 수는 없습니다. )

 

이러한, array 클래스 객체를 생성하려면 다음과 같이 할 수 있습니다.

#include <array>

int c_arr[] = { 1,2,3,4,5 }; // c-style array
std::array< int, 5 > arr0{ 1, 2, 3, 4, 5 };

 

그리고, 이전 항목에서 작성했던 함수 템플릿 pow2를 '컴파일 시 상수( constexpr )'로 변경하면, 이를 이용해서도 array 클래스를 생성할 수 있습니다.

template< int N >   
constexpr int pow2(){   // 컴파일 시 상수로 선언
    int val = std::pow(N, 2);
    return val;
}

int main(){

    std::array<int, pow2<2>() > arr;
    int cnt = 0;
    for( auto& x : arr){	// 범위기반 for
        x = cnt++;
    }

    for( int i = 0, n = arr.size(); i < n; i++){
        cout << arr[i] << " ";
    }
    cout << endl;
}

▼출력

0 1 2 3

위에서, 컴파일 시   pow2<2>()   표현식의 값을   4   로 평가할 수 있습니다.

따라서, 컴파일러는 std::array 템플릿 정의에서 클래스를 생성할 때, 비-형식 템플릿 매개변수   _Nm    를 이 값으로 대체합니다.

 

이 글과 관련있는 글들

함수 템플릿의 특수화( function template specialization )

 

 

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