C++ Template Basics
1. 템플릿 매개변수의 종류: 타입 vs. 비-타입
C++ 템플릿은 두 가지 주요 종류의 매개변수를 허용합니다.
| 종류 | 키워드 | 전달되는 것 | 사용 예시 |
|---|---|---|---|
| 타입 매개변수 (Type Parameter) | typename 또는 class |
임의의 데이터 타입 (int, std::string, 사용자 정의 클래스 등) |
template <typename T> |
| 비-타입 매개변수 (Non-Type Parameter) | 기본 타입 (int, bool, size_t 등) |
컴파일 시간에 알려진 값 (상수) | template <int N> |
2. 템플릿 매개변수를 선언하는 다양한 방법
템플릿 매개변수를 선언할 때는 이름(Identifier)과 기본값(Default Value)의 유무에 따라 다섯 가지 주요 패턴이 나타나며, 각각의 사용 목적이 다릅니다.
2.1. 비-타입 템플릿 매개변수 (Non-Type Template Parameters)
이 매개변수들은 컴파일 시간에 알려진 값(Value)을 템플릿에 전달합니다. (예: 배열 크기, 상수 플래그 등)
template <int N>
class A { int v{N}; }; // N을 내부에서 사용
template <int N = 10>
class B { int v{N}; }; // N을 내부에서 사용, 기본값 제공
template <int = 10>
class C { int v{0}; }; // 이름 없고, 기본값만 제공 (SFINAE 용)
template <int>
class D { int v{0}; }; // 이름도 기본값도 없음 (형식 충족 용)
template <int, int>
class E { int v{0}; }; // 여러 개를 나열
int main()
{
A<10> a{};
B<> b{};
C<> c{};
D<10> d{};
E<10, 10> e{};
}
| 클래스 | 선언 (template <…>) | 특징 및 역할 | 활용 예시 |
|---|---|---|---|
| A | template <int N> |
이름 있음, 기본값 없음. 가장 일반적인 형태이며, 전달된 값을 내부 로직에 사용합니다. | std::array<int, 5>의 5와 같이 크기를 전달할 때 |
| B | template <int N = 10> |
이름 있음, 기본값 있음. 인수가 없을 경우 기본값을 사용하여 편의성을 높입니다. | 옵션 플래그가 있는 템플릿 |
| C | template <int = 10> |
이름 없음, 기본값 있음. 전달된 값을 사용하지 않으며, 인수를 생략하기 위해 기본값을 제공합니다. std::enable_if의 논리 제어 게이트 역할을 위해 사용됩니다. |
std::enable_if를 사용한 함수 오버로딩 |
| D | template <int> |
이름 없음, 기본값 없음. 전달된 값을 사용하지 않으며, 오직 형식적으로 매개변수가 하나 필요함을 나타냅니다. | 거의 사용되지 않음 (A가 더 명확함) |
2.2. 타입 템플릿 매개변수 (Type Template Parameters)
이 매개변수들은 타입(Type)을 템플릿에 전달합니다. (예: int, float, 사용자 정의 구조체 등)
template <typename T>
class A { T v{0}; }; // T를 내부에서 사용
template <typename T = int>
class B { T v{0}; }; // T를 내부에서 사용, 기본값 제공
template <typename = int>
class C { int v{0}; }; // 이름 없고, 기본값만 제공 (SFINAE 용)
template <typename>
class D { int v{0}; }; // 이름도 기본값도 없음 (형식 충족 용)
template <typename, typename>
class E { int v{0}; }; // 여러 개를 나열
int main()
{
A<int> a{};
B<> b{};
C<> c{};
D<int> d{};
E<int, int> e{};
}
| 클래스 | 선언 (template <…>) | 특징 및 역할 | 활용 예시 |
|---|---|---|---|
| A | template <typename T> |
이름 있음, 기본값 없음. 가장 일반적인 형태이며, 전달된 타입을 내부에서 사용합니다. | std::vector<int>의 int와 같이 컨테이너의 타입을 정의할 때 |
| B | template <typename T = int> |
이름 있음, 기본값 있음. 인수가 없을 경우 기본 타입(int)을 사용하여 편의성을 높입니다. |
std::map의 기본 비교자(Compare = std::less<Key>) |
| C | template <typename = int> |
이름 없음, 기본값 있음. 전달된 타입을 사용하지 않으며, 인수를 생략하기 위해 기본값을 제공합니다. std::enable_if의 핵심 패턴입니다. |
std::enable_if를 사용한 함수 오버로딩 |
| D | template <typename> |
이름 없음, 기본값 없음. 오직 형식적으로 타입 매개변수가 하나 필요함을 나타냅니다. | 거의 사용되지 않음 |
3. 핵심 패턴: std::enable_if와 이름 없는 매개변수 (Class C)
std::enable_if는 템플릿의 추가 매개변수 위치에 삽입되어, 조건에 따라 해당 템플릿을 선택 후보에서 제외하는 방식으로 동작합니다 (SFINAE).
3.1. 패턴 1: 타입 템플릿 매개변수 사용 (Type Template Parameter)
이 방식은 추가적인 타입 매개변수를 선언하고, 그 기본값을 std::enable_if_t의 결과로 설정합니다.
#include <type_traits> // std::is_integral, std::enable_if_t
// T가 정수형일 때만 이 함수가 유효함
template <typename T,
typename = std::enable_if_t<std::is_integral<T>::value, float> // (A)
>
void check_type_foo()
{
std::cout << "Pattern 1: T는 정수형입니다 (타입 매개변수 사용)" << std::endl;
}
// 사용 예시
check_type_foo<int>(); // (A)가 float으로 치환되어 성공
// check_type_foo<float>(); // (A)가 치환 실패(SFINAE)하여 컴파일 에러
동작 원리 분석:
-
성공 시 (T = int):
std::enable_if_t<true, float>$\to$float타입으로 치환.- 최종 템플릿 선언은
template <typename T, typename = float>이 되어 유효한 Class C 타입 패턴이 완성됩니다.
- 최종 템플릿 선언은
-
실패 시 (T = float):
std::enable_if_t<false, float>$\to$type멤버를 찾을 수 없어 치환 실패가 발생하며, 이 함수는 컴파일러의 후보 목록에서 제외됩니다.
3.2. 패턴 2: 비-타입 템플릿 매개변수 사용 (Non-Type Template Parameter)
이 방식은 추가적인 비-타입 매개변수를 선언하고, 그 타입을 std::enable_if_t의 결과로 설정하며 기본 값(= true)을 할당합니다
#include <type_traits> // std::is_integral, std::enable_if_t
// T가 정수형일 때만 이 함수가 유효함
template <typename T,
std::enable_if_t<std::is_integral<T>::value, bool> = true // (B)
>
void check_type_bar()
{
std::cout << "Pattern 2: T는 정수형입니다 (비-타입 매개변수 사용)" << std::endl;
}
// 사용 예시
check_type_bar<long>(); // (B)가 bool = true로 치환되어 성공
// check_type_bar<double>(); // (B)가 치환 실패(SFINAE)하여 컴파일 에러
동작 원리 분석:
-
성공 시 (T = long):
std::enable_if_t<true, bool>$\to$bool타입으로 치환.- 최종 템플릿 선언은
template <typename T, bool = true>이 되며, 이는 유효한 Class C 비-타입 패턴이 완성됩니다.
- 최종 템플릿 선언은
-
실패 시 (T = double):
std::enable_if_t<false, bool>$\to$type멤버를 찾을 수 없어 치환 실패가 발생하며, 이 함수는 후보 목록에서 제외됩니다.
두 패턴의 차이점 및 장점
| 특징 | 패턴 1: 타입 매개변수 (typename = …) | 패턴 2: 비-타입 매개변수 (Type = Value) |
|---|---|---|
| 선언 형태 | 타입 매개변수의 기본값을 조작 | 비-타입 매개변수의 타입을 조작 |
| 다중 오버로드 | 동일한 타입 시그니처로 간주되어 컴파일 오류 발생 (함수 foo를 두 번 선언하는 경우) |
서로 다른 비-타입 매개변수 기본값을 가질 수 있어 템플릿 오버로딩에 적합 |
| 가독성 | 단순함 | Type = Value 형태가 약간 복잡함 |
Reference
https://leimao.github.io/blog/CPP-Enable-If/