숏컷 코드
// 함수 템플릿
template <typename T>
T maxOf(T a, T b) {
return a > b ? a : b;
}
// 클래스 템플릿
template <typename T>
class Box {
public:
explicit Box(T value) : value_{value} {}
T get() const { return value_; }
private:
T value_;
};템플릿은 "타입마다 같은 구조를 복붙하지 않기 위한 문법"이라고 먼저 보면 쉽습니다. 중요한 건 문법 암기보다, 함수 템플릿과 클래스 템플릿이 어디서 갈리는지입니다.
문법
먼저 다시 보는 기본형은 아래입니다.
- 함수 템플릿:
template <typename T> T f(T x) - 클래스 템플릿:
template <typename T> class Box - 특수화:
template <> - 다중 타입 매개변수
- 비타입 매개변수
어떤 템플릿 형태를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 같은 함수 구조를 타입별로 재사용 | 함수 템플릿 |
| 타입을 품은 자료구조 | 클래스 템플릿 |
| 특정 타입만 다르게 처리 | 특수화 |
| 타입에 요구 조건이 있음 | requires / concept |
| 크기 같은 상수도 매개변수화 | 비타입 매개변수 |
함수 템플릿
컴파일러가 호출 인수 타입을 보고 T를 자동으로 추론합니다. 명시적으로 지정할 수도 있습니다.
std::cout << maxOf(10, 20); // T = int (추론)
std::cout << maxOf(3.14, 2.71); // T = double (추론)
std::cout << maxOf<int>(10, 20); // T = int (명시)// ❌ 타입이 섞이면 T 하나로 추론되지 않음
// maxOf(1, 2.5);
// ✅ 호출 쪽에서 타입을 맞춰 준다
maxOf<double>(1, 2.5);클래스 템플릿
클래스 템플릿은 타입 인수를 항상 명시해야 합니다 (C++17부터 생성자를 통한 추론도 가능).
Box<int> intBox{42};
Box<double> dblBox{3.14};
Box autoBox{std::string{"hello"}}; // C++17 CTAD
// 멤버 함수를 클래스 바깥에서 정의할 때
template <typename T>
T Box<T>::get() const { return value_; }다중 타입 매개변수
template <typename Key, typename Value>
class Pair {
public:
Key key;
Value value;
};
Pair<std::string, int> entry{"score", 100};명시적 특수화 — 특정 타입에 다른 구현
// 기본 템플릿
template <typename T>
std::string describe(T val) {
return "value: " + std::to_string(val);
}
// bool 타입 전용 특수화
template <>
std::string describe<bool>(bool val) {
return val ? "true" : "false";
}비타입 매개변수 (Non-type parameter)
template <typename T, int N>
class FixedArray {
T data_[N];
public:
int size() const { return N; }
};
FixedArray<int, 5> arr; // 크기 5짜리 int 배열부분 특수화 — 타입 조건별 구현
전체 특수화(template <>)는 모든 타입 인수를 고정하지만, 부분 특수화는 일부 인수만 고정합니다. 클래스 템플릿에만 가능합니다.
// 기본 템플릿
template <typename T>
struct IsPointer { static const bool value = false; };
// T*에 대한 부분 특수화
template <typename T>
struct IsPointer<T*> { static const bool value = true; };
IsPointer<int>::value; // false
IsPointer<int*>::value; // true — 부분 특수화 선택variadic 템플릿 — 임의 개수의 타입 인수
// 기저 조건 (인수 0개)
template <typename... Ts>
void print() {} // 빈 팩 처리
// 재귀 전개
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // 하나씩 줄여가며 재귀
}
print(1, "hello", 3.14); // "1 hello 3.14 "
// C++17 fold expression — 재귀 없이
template <typename... Ts>
void printFold(Ts... args) {
((std::cout << args << " "), ...); // 쉼표 fold
}Concepts — requires로 제약 명시 (C++20)
#include <concepts>
// Numeric 타입만 받는 함수 템플릿
template <typename T>
requires std::is_arithmetic_v<T>
T add(T a, T b) { return a + b; }
// 축약 문법 (auto 파라미터)
auto addShort(std::integral auto a, std::integral auto b) {
return a + b;
}
// 커스텀 concept
template <typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};
template <Printable T>
void log(const T& val) { std::cout << val << "\n"; }체크포인트
- 타입마다 같은 함수를 반복한다: 함수 템플릿
- 타입을 품은 구조를 만든다: 클래스 템플릿
- 특정 타입만 다르게 처리한다: 특수화
- 타입 제약이 필요하다: Concepts /
requires - 구현은 보통 헤더에 둔다
주의할 점
템플릿 정의는 컴파일러가 인스턴스화 시점에 전체 코드를 볼 수 있어야 합니다. 함수/클래스 템플릿의 구현을 .cpp에 숨기면 링크 오류가 발생하므로, 일반적으로 헤더 파일에 선언과 정의를 모두 작성합니다.
오류 메시지가 길고 추적하기 어려울 수 있습니다. C++20 Concepts(requires 키워드)를 쓰면 훨씬 명확한 오류를 얻을 수 있습니다.
참고 링크
1 sources