C++ 템플릿 프로그래밍 완전 가이드
개요 — 템플릿이란
Section titled “개요 — 템플릿이란”템플릿(Template)은 타입을 파라미터로 받는 코드 생성 도구입니다. 동일한 로직을 int, float, std::string 등 다양한 타입에 대해 중복 없이 작성할 수 있습니다. 컴파일러가 사용 시점에 실제 타입을 대입해 코드를 생성하므로 런타임 오버헤드가 없습니다.
1. 함수 템플릿
Section titled “1. 함수 템플릿”1.1 기본 선언
Section titled “1.1 기본 선언”// 타입 파라미터 T를 받는 함수 템플릿template<typename T>T Max(T A, T B){ return (A > B) ? A : B;}
// 사용 — 컴파일러가 T를 추론int MaxInt = Max(3, 7); // T = intdouble MaxDouble = Max(3.14, 2.71); // T = double// Max(3, 3.14); // 오류 — T 추론 불가 (int vs double)
// 명시적 타입 지정double Result = Max<double>(3, 3.14); // T = double 강제1.2 복수 타입 파라미터
Section titled “1.2 복수 타입 파라미터”template<typename T, typename U>auto Add(T A, U B) -> decltype(A + B){ return A + B;}
auto Result = Add(3, 3.14); // T=int, U=double, 반환 double
// C++14: auto 반환 타입 추론template<typename T, typename U>auto Multiply(T A, U B){ return A * B;}1.3 비타입 파라미터
Section titled “1.3 비타입 파라미터”// 값을 파라미터로 받는 템플릿template<typename T, int N>void PrintArray(const T (&Arr)[N]){ for (int i = 0; i < N; ++i) { std::cout << Arr[i] << " "; } std::cout << "\n";}
int Numbers[] = {1, 2, 3, 4, 5};PrintArray(Numbers); // T=int, N=5 (자동 추론)2. 클래스 템플릿
Section titled “2. 클래스 템플릿”2.1 기본 선언
Section titled “2.1 기본 선언”// 고정 크기 스택 자료구조template<typename T, int Capacity = 64>class TStack{public: void Push(const T& Value) { if (Size < Capacity) { Data[Size++] = Value; } }
T Pop() { return Data[--Size]; }
const T& Top() const { return Data[Size - 1]; } bool IsEmpty() const { return Size == 0; } int Num() const { return Size; }
private: T Data[Capacity]; int Size = 0;};
// 사용TStack<int> IntStack; // TStack<int, 64>TStack<float, 16> FloatStack; // 용량 16TStack<std::string> StringStack;2.2 멤버 함수 외부 정의
Section titled “2.2 멤버 함수 외부 정의”template<typename T>class TBox{public: explicit TBox(T Value); T Get() const; void Set(T Value);
private: T Value;};
// 외부 정의 시 template<> 반복 필요template<typename T>TBox<T>::TBox(T InValue) : Value(InValue) {}
template<typename T>T TBox<T>::Get() const { return Value; }
template<typename T>void TBox<T>::Set(T InValue) { Value = InValue; }3. 템플릿 특수화
Section titled “3. 템플릿 특수화”3.1 명시적(전체) 특수화
Section titled “3.1 명시적(전체) 특수화”특정 타입에 대해 완전히 다른 구현을 제공합니다.
// 일반 템플릿template<typename T>struct TypeName{ static const char* Get() { return "unknown"; }};
// int에 대한 명시적 특수화template<>struct TypeName<int>{ static const char* Get() { return "int"; }};
// float에 대한 명시적 특수화template<>struct TypeName<float>{ static const char* Get() { return "float"; }};
std::cout << TypeName<int>::Get(); // "int"std::cout << TypeName<double>::Get(); // "unknown"3.2 부분 특수화 (클래스 템플릿만 가능)
Section titled “3.2 부분 특수화 (클래스 템플릿만 가능)”// 일반 템플릿template<typename T, typename U>struct Pair{ T First; U Second; void Print() { std::cout << First << ", " << Second; }};
// 두 타입이 같을 때 부분 특수화template<typename T>struct Pair<T, T>{ T First; T Second; void Print() { std::cout << "(same type) " << First << ", " << Second; } T Sum() const { return First + Second; } // 추가 기능};
// 포인터 타입에 대한 부분 특수화template<typename T>struct Pair<T*, T*>{ T* First; T* Second; void Print() { std::cout << "pointer pair"; }};
Pair<int, double> P1; // 일반Pair<int, int> P2; // 부분 특수화 — Sum() 사용 가능Pair<int*, int*> P3; // 포인터 특수화4. SFINAE — Substitution Failure Is Not An Error
Section titled “4. SFINAE — Substitution Failure Is Not An Error”타입 대입이 실패해도 오류가 아니라 해당 오버로드를 후보에서 제거합니다. 타입 조건에 따라 함수 오버로드를 선택하는 데 활용합니다.
4.1 std::enable_if
Section titled “4.1 std::enable_if”#include <type_traits>
// 정수 타입일 때만 활성화template<typename T>typename std::enable_if<std::is_integral<T>::value, T>::typeDouble(T Value){ return Value * 2;}
// 부동소수점 타입일 때만 활성화template<typename T>typename std::enable_if<std::is_floating_point<T>::value, T>::typeDouble(T Value){ return Value * 2.0;}
auto A = Double(5); // 정수 버전auto B = Double(3.14); // 부동소수점 버전// Double("hello"); // 오류 — 어떤 버전도 매칭 안 됨4.2 C++17 스타일 — std::enable_if_t
Section titled “4.2 C++17 스타일 — std::enable_if_t”// 더 간결한 C++17 문법template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>void Process(T Value){ std::cout << "정수 처리: " << Value;}
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>void Process(T Value){ std::cout << "실수 처리: " << Value;}5. if constexpr — 컴파일 타임 조건 분기 (C++17)
Section titled “5. if constexpr — 컴파일 타임 조건 분기 (C++17)”SFINAE보다 훨씬 읽기 쉬운 컴파일 타임 분기입니다.
#include <type_traits>
template<typename T>void Describe(T Value){ if constexpr (std::is_integral_v<T>) { std::cout << "정수: " << Value << ", 비트 수: " << sizeof(T) * 8; } else if constexpr (std::is_floating_point_v<T>) { std::cout << "실수: " << std::fixed << Value; } else if constexpr (std::is_pointer_v<T>) { std::cout << "포인터: " << static_cast<void*>(Value); } else { std::cout << "기타 타입"; }}
Describe(42); // 정수: 42, 비트 수: 32Describe(3.14); // 실수: 3.140000// if constexpr로 타입별 직렬화 구현template<typename T>std::string Serialize(const T& Value){ if constexpr (std::is_arithmetic_v<T>) { return std::to_string(Value); } else if constexpr (std::is_same_v<T, std::string>) { return "\"" + Value + "\""; } else { return Value.ToString(); // 커스텀 타입의 ToString() 호출 }}6. 가변 인자 템플릿 (Variadic Templates, C++11)
Section titled “6. 가변 인자 템플릿 (Variadic Templates, C++11)”임의 개수의 타입 파라미터를 받습니다.
// 재귀 종료 함수void Print() {} // 파라미터 없을 때 종료
// 가변 인자 템플릿 함수template<typename T, typename... Args>void Print(T First, Args... Rest){ std::cout << First; if constexpr (sizeof...(Rest) > 0) { std::cout << ", "; Print(Rest...); // 재귀 펼치기 }}
Print(1, 3.14, "hello", true); // 출력: 1, 3.14, hello, 1// 폴드 표현식 (C++17) — 재귀 없이 더 간결하게template<typename... Args>auto Sum(Args... Values){ return (... + Values); // 단항 폴드: ((V1 + V2) + V3) + ...}
auto Total = Sum(1, 2, 3, 4, 5); // 15
// 출력 폴드template<typename... Args>void PrintAll(Args... Values){ ((std::cout << Values << " "), ...); // 쉼표 연산자 폴드}
PrintAll(1, "hello", 3.14); // 1 hello 3.14// 완벽 전달 + 가변 인자 — std::make_unique 스타일template<typename T, typename... Args>std::unique_ptr<T> Make(Args&&... args){ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}7. 타입 특성 (Type Traits) 활용
Section titled “7. 타입 특성 (Type Traits) 활용”#include <type_traits>
// 자주 쓰는 type traitsstatic_assert(std::is_integral_v<int>); // truestatic_assert(std::is_floating_point_v<float>); // truestatic_assert(std::is_pointer_v<int*>); // truestatic_assert(std::is_same_v<int, int>); // truestatic_assert(std::is_base_of_v<Base, Derived>); // truestatic_assert(std::is_constructible_v<MyClass, int, float>); // 생성자 존재 확인
// 타입 변환using NoRef = std::remove_reference_t<int&>; // intusing NoCV = std::remove_cv_t<const int>; // intusing Decay = std::decay_t<int[5]>; // int*using Common = std::common_type_t<int, double>; // double8. 실전 패턴 — 타입 안전 싱글턴
Section titled “8. 실전 패턴 — 타입 안전 싱글턴”template<typename T>class TSingleton{public: TSingleton(const TSingleton&) = delete; TSingleton& operator=(const TSingleton&) = delete;
static T& Get() { static T Instance; // C++11: 스레드 안전 정적 초기화 return Instance; }
protected: TSingleton() = default; virtual ~TSingleton() = default;};
// 사용class AudioManager : public TSingleton<AudioManager>{ friend class TSingleton<AudioManager>; AudioManager() = default;
public: void PlaySound(const std::string& SoundID) { /* ... */ }};
AudioManager::Get().PlaySound("explosion");| 기능 | 사용 시점 |
|---|---|
| 함수 템플릿 | 타입만 다른 동일 로직 |
| 클래스 템플릿 | 타입 파라미터화된 자료구조·컨테이너 |
| 명시적 특수화 | 특정 타입에 완전히 다른 구현 |
| 부분 특수화 | 포인터·동일 타입 등 패턴 기반 다른 구현 |
if constexpr | 컴파일 타임 타입 분기 (C++17 권장) |
std::enable_if | SFINAE 기반 오버로드 선택 (C++11/14) |
| Variadic Templates | 임의 개수 타입 파라미터 |
핵심 규칙:
- 템플릿 정의는 헤더 파일에 위치해야 합니다 (링커가 정의를 찾을 수 없음)
- C++17 이상에서는 SFINAE 대신
if constexpr을 우선 사용 sizeof...(Args)로 파라미터 팩의 원소 수를 컴파일 타임에 확인