1학년 / 2학기 / C++ / 7강
템플릿
템플릿
학습 흐름
함수 템플릿 → 자료형 추론과 명시적 지정 → 일반 함수와의 우선순위
↓
클래스 템플릿 → 비자료형 매개변수 (int N)
↓
템플릿 특수화 template<> → 템플릿 재귀 Factorial<N>, Factorial<0>1. 함수 템플릿
자료형만 다르고 코드가 같은 함수를 하나의 틀로 씁니다. 컴파일러가 호출할 때의 자료형으로 실제 함수를 만들어 냅니다(인스턴스화).
template <typename T> // typename 대신 class도 같은 뜻
void change(T& a, T& b) { // 원본을 바꾸려면 참조 매개변수
T temp = a;
a = b;
b = temp;
}
int a = 1, b = 2;
change(a, b); // T = int
double x = 1.5, y = 2.5;
change(x, y); // T = double
string s = "hi", t = "yo";
change(s, t); // T = string자료형 추론과 명시적 지정
template <typename T>
T maximum(T a, T b) { return a > b ? a : b; }| 호출 | T | 결과 |
|---|---|---|
maximum(3, 7) | int | 7 |
maximum('a', 'b') | char | b |
maximum(3, 4.5) | int? double? | 컴파일 오류 (T 하나에 서로 다른 자료형) |
maximum<double>(3, 4.5) | double (명시) | 4.5 |
maximum<int>(3, 4.9) | int (명시) | 4 (4.9가 int로 잘림) |
maximum(string("apple"), string("banana")) | string | banana (사전순) |
일반 함수가 있으면 일반 함수가 먼저
template <typename T> void show(T) { cout << "T "; }
void show(int) { cout << "int "; }
show(1); // int ← 정확히 맞는 일반 함수 우선
show(1.0); // T
show('a'); // T ← char는 템플릿이 정확히 맞음
show<int>(1); // T ← <>를 쓰면 템플릿을 강제자료형마다 다른 함수가 만들어진다
template <typename T>
int counter(T) { static int c = 0; return ++c; }
cout << counter(1) << counter(2) << counter(1.0) << counter('a') << counter(3);
// 출력: 12113counter<int>, counter<double>, counter<char>는 서로 다른 함수이므로 static 변수도 각각 따로 있습니다.
위 maximum, show, counter를 사용할 때 출력을 쓰고, 컴파일 오류인 줄을 고르시오.
cout << maximum(3, 7) << ' ' << maximum<int>(3, 4.9) << ' '
<< maximum<double>(3, 4.5) << '\n'; // (1)
cout << maximum(3, 4.5); // (2)
show(1); show(1.0); show<int>(1); // (3)
cout << '\n' << counter(5) << counter(5.0) << counter(6); // (4)정답 확인
(2)는 컴파일 오류입니다(T를 정할 수 없음). 나머지 출력:
7 4 4.5
int T T
112(4)는 왼쪽부터 counter(5) → counter<int>의 첫 호출 1, counter(5.0) → counter<double>의 첫 호출 1, counter(6) → 다시 counter<int>이므로 2입니다. 이 줄이 프로그램에서 counter를 처음 부른다고 가정합니다.
2. 클래스 템플릿
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
void set(T v) { value = v; }
};
Box<int> bi(3); // 자료형을 <>로 지정
Box<string> bs("cpp");
template <typename T1, typename T2>
class Pair {
public:
T1 first;
T2 second;
Pair(T1 a, T2 b) : first(a), second(b) {}
};
Pair<string, int> p("age", 20);Box<int>와Box<double>은 서로 다른 클래스입니다.- 클래스 밖에서 멤버 함수를 정의할 때는
template <typename T> T Box<T>::get() const { … }처럼 다시 템플릿 머리를 씁니다. - C++14까지는
Box<int>처럼 자료형을 반드시 써야 합니다. (C++17부터는Box b(3);처럼 생략도 가능)
비자료형 매개변수
템플릿 매개변수로 정수 상수를 받을 수 있습니다. 컴파일할 때 정해지므로 배열 크기로도 쓸 수 있습니다.
template <typename T, int N>
class Stack {
T data[N];
int top = 0;
public:
bool push(T v) { if (top == N) return false; data[top++] = v; return true; }
T pop() { return data[--top]; }
int size() const { return top; }
};
Stack<int, 3> st;
cout << st.push(1) << st.push(2) << st.push(3) << st.push(4); // 1110
cout << ' ' << st.pop() << st.pop() << ' ' << st.size(); // 32 13. 템플릿 특수화
특정 자료형에 대해서만 다른 구현을 줄 때 template <>를 씁니다.
template <typename T>
class Printer {
public:
void print(T v) { cout << "value " << v << '\n'; }
};
template <> // bool일 때만 따로
class Printer<bool> {
public:
void print(bool v) { cout << (v ? "yes" : "no") << '\n'; }
};
Printer<int>().print(5); // value 5
Printer<bool>().print(true); // yes4. 템플릿 재귀: Factorial
template <long N>
class Factorial {
public:
long Value(void) { return N * fn_1.Value(); }
private:
Factorial<N - 1> fn_1; // 한 단계 작은 클래스를 멤버로 가짐
};
template <>
class Factorial<0> { // 특수화 = 재귀의 기저 조건
public:
long Value(void) { return 1; }
};
Factorial<5> f;
cout << f.Value(); // 120Factorial<5> 안에 Factorial<4> 안에 … Factorial<1> 안에 Factorial<0>
Value: 5 × 4 × 3 × 2 × 1 × 1 = 120같은 방식으로 정의한 다음 템플릿의 값을 구하시오.
template <int N> struct Fib { static const int value = Fib<N - 1>::value + Fib<N - 2>::value; };
template <> struct Fib<0> { static const int value = 0; };
template <> struct Fib<1> { static const int value = 1; };
template <int N> struct Pow2 { static const int value = 2 * Pow2<N - 1>::value; };
template <> struct Pow2<0> { static const int value = 1; };
cout << Fib<6>::value << ' ' << Fib<10>::value << ' ' << Pow2<10>::value;Fib에서 Fib<1> 특수화를 지우면 어떻게 되는가?
정답 확인
8 55 1024.
Fib<1>이 없으면 Fib<1>이 일반 템플릿으로 만들어져 Fib<0> + Fib<-1>을 요구하고, Fib<-1>, Fib<-2>, …로 끝없이 내려가 컴파일 오류가 납니다. 두 칸 전을 쓰는 재귀는 기저 조건이 두 개 필요합니다.
핵심 정리
- 함수 템플릿은 호출한 자료형마다 별도의 함수가 만들어진다(static 변수도 따로).
- 원본을 바꾸는 템플릿은
T&매개변수. 리터럴은 넘길 수 없다. - 자료형 추론은 변환하지 않는다. 서로 다른 자료형을 섞으면
<double>처럼 명시한다. - 정확히 맞는 일반 함수가 템플릿보다 우선한다.
f<int>()는 템플릿을 강제한다. - 클래스 템플릿은
Box<int>처럼 자료형을 지정한다.int N같은 상수 매개변수도 가능하다. template <>특수화로 특정 자료형만 다르게 구현한다.Factorial<0>은 템플릿 재귀의 기저 조건이다. 없으면 컴파일 오류(인스턴스화 깊이 초과).
오늘도 한 걸음 앞으로.
내용을 이해했다면 학습 완료를 체크해 주세요.