참조 : 씹어먹는 C++ - <9 - 4. 템플릿 메타 프로그래밍 2>
기본
- 템프릿은 컴파일 시에 아무런 코드로 변환X
- 문법 검사만 실행
- 인스턴스화 했을 떄, 변환
typename T = int
와 같이 디폴트 값 설정 가능
클래스 템플릿
template <typename T> // <- <class T> 보다 typename 권장 class Vector { T* data; } // 부분 특수화 template <typename B> class test<int, B, double> {}; // 완전 특수화 template <> class test<int, int, double> {};
명시적 템플릿 인스턴스화
일반적으로 클래스 템플릿은 객체 생성과 타입 지정이 명시적으로 이루어져야 하는 경우가 많음.
- 중복 코드 방지 (같은 타입의 클래스가 여러 파일에서 중복 인스턴스화 문제)
- ODR(One Definition Rule) 위반 링크 에러 가능성 높음
함수 템플릿은 암시적 인스턴스화가 유연하게 잘 동작
- 링크 단계에서 대부분 하나로 병합됨.
- 즉, 컴파일러에서 최적화를 잘 해줌
명시적 템플릿 인스턴스화 예시
// 헤더 파일 template<typename T> class MyTemplate { public: void func(); }; // 명시적 선언 extern template class MyTemplate<int>; // 소스 파일 template class MyTemplate<int>;
함수 템플릿
기본 사용
template <typename T> T max(T& a, T& b) { return a > b ? a : b; }
Functor
: 함수는 아니지만 함수 인 척을 하는 객체template <typename Cont, typename Comp> void sort(Cont& cont, Comp& comp) { if(!comp(cont[1], cont[0])) cont.swap(); } struct Comp1 { bool operator()(int a, int b) { return a > b; } }; Comp1 comp1; sort(int_vec, comp1);
디폴트 템플릿 인자 : 입력값 없을 떄, 기본 타입 지정
template <typename Cont = int>
템플릿 비타입 매개변수 : 컴파일 시간에 결정되는 정적 값
template <typename Cont, int num = 5>
팩토리얼 구현 예시
#include <iostream> // 기본 템플릿 선언 template <int N> struct Factorial { static constexpr int value = N * Factorial<N - 1>::value; }; // 특수화: N == 0일 때 재귀 종료 template <> struct Factorial<0> { static constexpr int value = 1; }; int main() { // Factorial<3>의 결과 출력 std::cout << "Factorial<3> = " << Factorial<3>::value << std::endl; // 출력: 6 return 0; }
다양한 타입의 복제, 대입 생성자
- 만약,
double
타입과int
타입의 템플릿으로 생성된 인스턴스가 있을 때는 다음과 같이 복제, 대입 연산자를 선언해야 한다.template <typename T> class Grid { public: template <typename E> Grid<const Grid<E>& src); template <typenmae E> Grid<T>& operator=(const Grid<E>& rhs); } /* 소스파일 */ template <typename T> template <typename E> // template <typename T, typename E> 와 같이 작성 불가 Grid<T>::Grid(const Grid<E>& src) : Grid { src.getWidth(), src.getHeight()} { ... } template <typename T> template <typename E> Grid<T>& Grid<T>::operator=(const Grid<E>& rhs) { ... }
- template <typename T, typename E> 와 같이 작성 불가ㅌ
가변길이 템플릿
- 템플릿 파라미터 팩(parameter pack)
template <typename T, typename... Types>
void print(T arg, Types... args)
- 이전
print(T arg)
가 있어야 됨.template <typename T> void print(T arg) { std::cout << arg << std::endl; } template <typename T, typename... Types> void print(T arg, Types... args) { std::cout << arg << ", "; print(args...); }
- 기저 템플릿 없이 사용할려면
template <typename T, typename... Types> void print(T arg, Types... args) { std::cout << arg << ", "; if constexpr (sizeof(args) > 0) print(args...); }
- Fold 방식 (C++17)
- 4가지 지원
- 괄호 필수
return (...+ nums); // Left Fold return (nums + ...); // Right Fold return (initial + ... + args); //Left Fold with Initial return (args + ... + initial); // Right Fold with Initial
TMP(Template Meta Programing)
예제
template <int N, int D = 1> struct Ratio { typedef Ratio<N, D> type; static const int num = N; // 분자 static const int den = D; // 분모 }; template <class R1, class R2> struct _Ratio_add { typedef Ratio<R1::num * R2::den + R2::num * R1::den, R1::den * R2::den> type; }; template <class R1, class R2> struct Ratio_add : _Ratio_add<R1, R2>::type {}; int main() { typedef Ratio<2, 3> rat; typedef Ratio<3, 2> rat2; typedef Ratio_add<rat, rat2> rat3; std::cout << rat3::num << " / " << rat3::den << std::endl; return 0; }
C++11 부터는
using
키워드로 대체가능typedef Ratio_add<rat, rat2> rat3; using rat3 = Ratio_add<rat, rat2>;
의존 타입은
typename
으로 타입임을 명시
where 제약 조건 사용법
C++20 이전 :
SFINAE (Substitution Failure Is Not An Error) 이용 <- 치환 실패 시, 오버로딩 후보에서 제외
enalbe_if
이용#include <iostream> #include <type_traits> // 첫 번째 조건: T1이 정수, T2가 실수 // 템플릿 파라미터에 조건 걸기 template <typename T1, typename T2, typename std::enable_if<std::is_integral<T1>::value, void>::type* = nullptr, typename std::enable_if<std::is_floating_point<T2>::value, void>::type* = nullptr> void func(T1 a, T2 b) { std::cout << "정수: " << a << ", 실수: " << b << std::endl; } // 템플릿 파라미터에 조건 걸기, 한 줄로 쓰기 template <typename T1, typename T2, typename std::enable_if<std::is_integral<T1>::value && std::is_floating_point<T2>::value, void>::type* = nullptr> void func(T1 a, T2 b) { std::cout << "정수: " << a << ", 실수: " << b << std::endl; } // 반환 타입으로 사용 template <typename T1, typename T2> typename std::enable_if<std::is_integral<T1>::value && std::is_floating_point<T2>::value, void>::type void func(T1 a, T2 b) { std::cout << "정수: " << a << ", 실수: " << b << std::endl; }
int main() {
func(42, 3.14); // 올바른 호출
// func(3.14, 42); // 오류: 첫 번째 인자가 실수이므로 조건 불충족
return 0;
}
```
C++20 이후 : Concepts
#include <iostream> #include <concepts> // 정수 타입을 요구하는 Concept template <typename T> concept Integer = std::is_integral_v<T>; // Integer Concept을 만족하는 타입만 허용 template <Integer T> T add(T a, T b) { return a + b; } int main() { std::cout << add(3, 4) << std::endl; // 올바른 호출, 정수 타입 // std::cout << add(3.5, 4.5) << std::endl; // 컴파일 오류, 실수 타입 return 0; }
'공부 > C++' 카테고리의 다른 글
문자열 (1) | 2025.03.14 |
---|---|
참조자(&) (0) | 2025.03.14 |
상속 (0) | 2025.03.07 |
foreach 방식 (범위 기반 for 문) (0) | 2025.03.05 |
(C++17) optional & variant (0) | 2025.03.05 |