Skip to content

C++ 템플릿 프로그래밍 완전 가이드

템플릿(Template)은 타입을 파라미터로 받는 코드 생성 도구입니다. 동일한 로직을 int, float, std::string 등 다양한 타입에 대해 중복 없이 작성할 수 있습니다. 컴파일러가 사용 시점에 실제 타입을 대입해 코드를 생성하므로 런타임 오버헤드가 없습니다.


// 타입 파라미터 T를 받는 함수 템플릿
template<typename T>
T Max(T A, T B)
{
return (A > B) ? A : B;
}
// 사용 — 컴파일러가 T를 추론
int MaxInt = Max(3, 7); // T = int
double 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 강제
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;
}
// 값을 파라미터로 받는 템플릿
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 (자동 추론)

// 고정 크기 스택 자료구조
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; // 용량 16
TStack<std::string> StringStack;
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; }

특정 타입에 대해 완전히 다른 구현을 제공합니다.

// 일반 템플릿
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”

타입 대입이 실패해도 오류가 아니라 해당 오버로드를 후보에서 제거합니다. 타입 조건에 따라 함수 오버로드를 선택하는 데 활용합니다.

#include <type_traits>
// 정수 타입일 때만 활성화
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
Double(T Value)
{
return Value * 2;
}
// 부동소수점 타입일 때만 활성화
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
Double(T Value)
{
return Value * 2.0;
}
auto A = Double(5); // 정수 버전
auto B = Double(3.14); // 부동소수점 버전
// Double("hello"); // 오류 — 어떤 버전도 매칭 안 됨
// 더 간결한 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, 비트 수: 32
Describe(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)...));
}

#include <type_traits>
// 자주 쓰는 type traits
static_assert(std::is_integral_v<int>); // true
static_assert(std::is_floating_point_v<float>); // true
static_assert(std::is_pointer_v<int*>); // true
static_assert(std::is_same_v<int, int>); // true
static_assert(std::is_base_of_v<Base, Derived>); // true
static_assert(std::is_constructible_v<MyClass, int, float>); // 생성자 존재 확인
// 타입 변환
using NoRef = std::remove_reference_t<int&>; // int
using NoCV = std::remove_cv_t<const int>; // int
using Decay = std::decay_t<int[5]>; // int*
using Common = std::common_type_t<int, double>; // double

8. 실전 패턴 — 타입 안전 싱글턴

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_ifSFINAE 기반 오버로드 선택 (C++11/14)
Variadic Templates임의 개수 타입 파라미터

핵심 규칙:

  • 템플릿 정의는 헤더 파일에 위치해야 합니다 (링커가 정의를 찾을 수 없음)
  • C++17 이상에서는 SFINAE 대신 if constexpr을 우선 사용
  • sizeof...(Args)로 파라미터 팩의 원소 수를 컴파일 타임에 확인