Function Template
함수 템플릿
- Generic Function(일반화 함수)을 서술하는 방법이다.
- Specific Type(\(\texttt{int}\)형이나 \(\texttt{double}\)형과 같은)* 을 포괄할 수 있는 Generic Type으로 함수를 정의하는 방법이다.
(하나의 케이스가 아닌, 다른 경우에도 대응하여 프로그래밍 하는 것을 Generic Programming(일반화 프로그래밍)이라고 한다.)
- 다양한 데이터형**에 대해 같은 알고리즘을 적용해야 할 경우에 유용한 수단이다.
- 템플릿을 Parameterized Type(매개변수화 데이터)이라고 하기도 한다.
- 여러 개의 함수 정의를 더 간단하고 신뢰성 높게 생성하게 해준다.
- 템플릿을 구현하는 것은, 함수를 정의하는 것과는 다른 개념이며, 함수를 정의하는 방법을 컴파일러에게 알려주는 것이며, 컴파일러는 매개변수의 데이터형을 검사하여 그에 대응하는 함수를 생성하게 된다.
- 함수 템플릿은 프로그래밍의 편의성을 제고하는 기능을 하며, 프로그램의 실행시간을 개선시켜 주지는 않는다.
* 특정한 데이터형들(\(\texttt{int}\)형, \(\texttt{double}\)형)을 Specific Type(구체형)이라 총칭하며,
템플릿 매개변수와 같이 많은 데이터형을 아우르는 데이터형들을 Generic Type(일반형)이라 총칭한다.
** 모든 데이터형이라고 하지 않은 이유는, 특정 배열이나, 특정 구조체, 특정 클래스 또한 데이터형 중에 하나라고 할 수 있는데, 이 모든 경우에 대해 모든 연산이 완벽히 작동하는 함수 템플릿은 없기 때문이다. (Template Limitation 개념)
따라서, 해당 템플릿의 구현 내용이 사용될법한 모든 데이터형을 적절히 처리할 수 있을지에 대한 판단은 프로그래머의 몫이다.
Function Template Implementation (함수 템플릿 구현방법)
- \(\texttt{template <class T>}\) 또는 \(\texttt{template <typename T>}\) 구문을 이용하며, 이 구문은 Function Prototype(함수 원형) 바로 윗 부분과 Function Definition(함수 정의) 바로 윗 부분에 반드시 서술되어야 한다.
- 위와 같은 문맥 상에서, \(\texttt{typename}\) 키워드는 \(\texttt{class}\) 키워드와 같은 동작을 수행하지만, 매개변수 \(\texttt{T}\)가 데이터형을 나타낸다는 사실을 더 분명하게 알려준다.
- 여기서, \(\texttt{T}\)는 임의의 데이터형을 의미하는 Parameter와 같은 역할을 하며 C++의 네이밍 규칙에 위배되지 않는 범위 내에서 아무 이름이나 사용할 수 있다.
- 아래 예시에서는, 두 매개변수 모두 템플릿 매개변수형이지만, 모든 템플릿 매개변수가 그래야할 필요는 없다.
template <class T>
void Func (T& a, T& b); // Function Prototype
...
template <class T>
void Func (T& a, T& b) { // Function Definition
T temp;
temp = a;
a = b;
b = temp;
}
...
func(val1, val2); // Call the Function
func<>(val3, val4); // Call the Function
// <>괄호는 컴파일러가 비템플릿 함수보다는 템플릿 함수를 우선적으로 선택하게 한다.
- 일반적으로, 함수 템플릿은 헤더파일에 정의해 놓는다.
Overloaded Template (템플릿의 오버로딩)
- 다양한 데이터형에 대해 같은 알고리즘을 구현하는 개념이 템플릿이지만, 모든 데이터형에 대해 항상 같은 알고리즘을 사용하는 경우는 흔치 않는데, 이런 경우에 템플릿 정의를 오버로딩하게 된다.
- 일반적인 함수 오버로딩의 경우와 마찬가지로, 확실하게 구분되는 Function Signature(매개변수 정보)를 사용해야 한다.
- 일반적인 함수 오버로딩의 경우와 마찬가지로, 함수의 이름을 일치시키고, Function Signature를 달리하여 구현하면 된다.
- \(\texttt{template <class T>}\) 구문을 함수 원형, 함수 정의 윗 부분에 기술해야 한다.
Explicit Specialization (명시적 특수화)
- 특정 데이터형에 특수화된 함수 정의를 제공하는 방법이다.
- 컴파일러가 함수 호출에 정확히 대응하는 특수화된 정의를 발견하면, 템플릿을 호출하지 않고 특수화된 정의를 함수를 호출하게 된다.
* 3rd Generation Specialization (3세대 특수화 형식)
(ISO/ANSI C++ Standard)
1. 한 함수 이름이 주어지면, 사용자는 템플릿 함수, 템플릿이 아닌 함수, 명시적 특수화 템플릿 함수 세 가지 모두를 정의할 수 있으며, 오버로딩 버전도 정의할 수 있다.
2. 명시적 특수화를 하기 위한 원형과 정의 앞에 \(\texttt{template <>}\) 구문이 있어야 하고, 그 특수형 함수의 이름이 와야 한다.
template <> returnType functinoName<specificDataType> (parameterList);
// 특수화된 데이터형을 생략하여 아래 구문처럼 간결하게 표현할 수 있다!
// (Function Signature를 통해 특수화된 데이터형을 알 수 있으므로)
template <> returnType functionName (parameterList);
3. 명시적 특수화 함수는 템플릿보다 Override(우선시)되고, 템플릿이 아닌 일반 함수는 세 함수 종류 중에서 최우선시 된다.
(우선순위 : 일반 함수(구체형 함수) > 명시적 특수화 함수 > 템플릿 함수)
Ex. 명시적 특수화 함수의 정의 예시
struct Student {
int ID;
string Name;
}
template <class T>
void Swap (T& a, T& b) {
T temp = a;
a = b;
b = temp; }
template <> void Swap<Student> (Student& s1, Student& s2) {
// Student형을 생략하고, template <> void Swap (Student&, Student&) 도 가능하다!
int temp = s1.ID; s1.ID = s2.ID; s2.ID = temp;
string temp = s1.Name; s1.Name = s2.Name; s2.Name = temp;
}
int main() {
double a = 12.34; double b = 34.56;
Swap(a, b); // 템플릿 함수 호출
struct Student c, d;
c.ID = 135; c.Name = "Charles";
d.id = 246; d.Name = "Emma";
Swap(c, d); // 명시적 특수화 함수 호출
...
}
Instantiation (템플릿의 구체화)
- 컴파일러가 특정 데이터형에 맞는 함수 정의를 생성하기 위해 템플릿을 사용하여 만드는 기법을 의미한다.
* Implicit Instantiation (암시적 구체화)
- 함수의 호출이 감지되면, 그제서야 그에 맞는 데이터형의 함수가 만들어 지는 것을 의미한다.
- 컴파일러가 함수 호출구문을 감지하고, 그에 맞는 함수 정의를 만들 필요성을 암시적으로 인식하는 것에서 착안된 용어이다.
* Explicit Instantiation (명시적 구체화)
- 프로그래머가 특정 데이터형에 대한 템플릿의 구체화를 직접 지시해서 함수가 만들어 지는 것을 의미한다.
template returnType functionName<specificDataType> (parameterList);
// 실 사용 예시
template void Swap<student> (student&, student&); // student는 임의의 구조체 타입이다.
Ex. 명시적 구체화의 사용 예시
template <class T>
T Add (T a, T b) {
return a + b; }
...
int m = 6; double x = 10.2;
cout << Add<double>(x, m) << endl;
// 명시적 구체화 덕에, 모호할 수 있었던 상황을 회피했다!
두 명시적 구체화 구문 \(\texttt{func<dataType *>(dataType *)}\) 구문과 \(\texttt{func<dataType>(dataType *)}\) 구문의 차이 알아내기!
Explicit Specialization vs Instantiation (명시적 특수화와 구체화)
- 명시적 특수화, 구체화(암시적 구체화, 명시적 구체화) 이 모두를 Specialization(특수화)라고 총칭한다.
- 이들의 공통점은 일반화 서술을 나타내는 함수 정의가 아니라, Specific Type을 위한 함수 정의라는 점이다.
- 같은 파일 내에서, 명시적 구체화와 명시적 특수화는 함께 사용될 수 없다.
Ex. 명시적 특수화와 명시적 구체화의 표기 차이 예시
template <> void Swap<int> (int&, int&); // 명시적 특수화 (기본)
template <> void Swap (int&, int&); // 명시적 특수화 (축약)
template void Swap<int> (int&, int&); // 명시적 구체화
// template 키워드 오른편에 <> 괄호의 유무 차이이다!
Partial Ordering Rule (부분순서화 규칙)
- 가장 특수화된 템플릿을 찾아내는 규칙을 지칭한다.
- Implicit Instantiation(암시적 구체화)와 마찬가지로, 부분순서화 규칙도 C++98 에서 새로 추가된 개념이다.
Ex. 부분순서화 규칙에 따른 템플릿 함수 선정 예시
template <typename T>
void showArray (T arr[], int n) { // #1 Template
using namespace std;
cout << "템플릿 A\n";
for (int i = 0; i < n; i++)
cout << arr[i] << ' ';
cout << endl;
}
template <typename T>
void showArray (T* arr[], int n) { // #2 Template
using namespace std;
cout << "템플릿 B\n";
for (int i = 0; i < n; i++)
cout << *arr[i] << ' ';
cout << endl;
}
...
int arr1[3];
double* arr2[3];
// 초기화 부분
showArray(arr1, 3); // #1 템플릿이 사용된다.
showArray(arr2, 3); // #2 템플릿이 사용된다.
// #1 템플릿이 선택되어, 배열의 원솟값 대신 주솟값이 출력될 수도 있었으나,
// #2 템플릿의 경우가 더 적은 횟수의 변환과정을 거치므로 #2 템플릿이 선택된 것이다.
\(\texttt{decltype}\) 키워드
- C++11에서 새로 정의된 키워드이다.
- 특정 변수와 동일한 데이터형의 변수를 선언할 때 쓰인다.
Ex. \(\texttt{decltype}\) 키워드 사용 예시
int x;
decltype(x) y; // 변수 x와 같은 데이터형의 변수 y를 선언한다.
decltype(x + y) xpy; // x + y와 같은 데이터형의 변수 xpy를 선언한다.
decltype(x - y) xmy = x - y; // 선언과 동시에 초기화도 가능하다.
* \(\texttt{decltype}\) 키워드의 일반형과 컴파일러가 적절한 데이터형을 선정하는 프로세스의 두 가지 버전
decltype (expression) var;
Stage 1. \(\texttt{expression}\)이 Unparenthesized Identifier(괄호 없는 식별자)일 경우, 변수 \(\texttt{var}\)은 식별자와 동일한 타입이 되며 \(\texttt{const}\)를 포함하게 된다.
Stage 2. \(\texttt{expression}\)이 함수 호출 구문일 경우, \(\texttt{var}\)은 함수 리턴형 타입이 된다.
- 이 경우에, 컴파일러는 리턴 타입을 얻기 위해 함수 원형만 검사하고 함수 호출은 수행하지 않는다.
Trailing Return Type
- \(\texttt{decltype}\) 키워드와 같이 C++11 에서 새로 정의된 개념이다.
- 리턴 데이터형의 선언을 추후로 미루어주는 역할을 한다.
- \(\texttt{auto}\) 데이터형이 임시방편의 역할을 수행한다.
- \(\texttt{->}\) 표기를 이용해 추후에 선택될 데이터형을 표기한다.
Ex. Trailing Return Type 표기 예시
double Func (int x, float y);
// 위 구문은 아래 구문과 같은 Trailing Return Type으로 대체될 수 있다.
auto Func (int x, float y) -> double;
// 두 구문은 논리적으로 같은 동작을 수행한다.
Ex. Trailing Return Type 기능에 \(\texttt{decltype}\) 키워드를 활용한 예시
- 두 종류의 템플릿 매개변수를 입력받아 더하는 함수의 경우 리턴형의 예측이 어려울 수 있는데, 이를 해결하는 예시이다.
- 매개변수가 먼저 선언되어야 하는 \(\texttt{decltype}\) 키워드의 한계를 \(\texttt{auto}\) 데이터형이 보완한다.
template <class T1, class T2>
auto Plus (T1 x, T2 y) -> decltype(x + y) {
....
return (x + y);
}
Template Aliases (템플릿 대용이름)
- C++11부터 템플릿을 통한 데이터형 지정자의 길이를 줄여 코딩의 편의성을 제고하는 기능들이 추가되었다.
1. \(\texttt{typedef}\) 키워드 이용
// typedef를 이용한 템플릿 특수화 대용이름 예시
typedef std::array<double, 12> arrad;
typedef std::array<int, 12> arri;
typedef std::array<std::string, 12> arrst;
arrd gallons; // std::array<double, 12> gallons;
arri days; // std::array<int, 12> days;
arrst months; // std::array<std::string, 12> months;
2. \(\texttt{using}\) 선언 이용
// using 선언을 이용한 템플릿 특수화 대용이름 예시
template <typename T>
using arrtype = std::array<T, 12>; // 템플릿 매개변수를 이용한다 -> 더 넓은 범위를 커버할 수 있다.
arrtype<double> gallons; // std::array<double, 12> gallons;
arrtype<int> days; // std::array<int, 12> days;
arrtype<std::string> months; // std::array<std::string, 12> months;
- C++11은 또한 \(\texttt{using = }\) 구문을 Non-Template(비 템플릿)까지 확장시켰다.
(이 경우에서는 \(\texttt{using =}\) 구문이 \(\texttt{typedef}\)과 동일한 기능을 한다.)
// using = 구문의 비 템플릿으로의 확장 예시
typedef const char*pc1;
using pc2 = const char*;
typedef const int*(*pa1)[10]
using pa2 = const int*(*)[10]