본 글은 코드누리의 Template Programming 강좌를 개인 학습 목적으로 정리한 글 입니다.
https://www.ecourse.co.kr/course-status/
템플릿 특수화란?
- 특정 타입에 대해 이렇게 처리 해주세요~ 하는 것
- 클래스를 만들어내는 틀을 필요에 맞게 교체하는 것
#include <iostream>
#include <string>
/* Primary template */
template<typename T>
class Stack
{
public:
void push(T a) { std::cout << "T" << std::endl; }
};
// 부분 특수화
template<typename T>
class Stack<T*>
{
public:
void push(T* a) { std::cout << "T*" << std::endl; }
};
// 특수화
template<>
class Stack<char*>
{
public:
void push(char* a) { std::cout << "char*" << std::endl; }
};
int main()
{
Stack<int> s1; s1.push(0);
Stack<int*> s2; s2.push(0);
Stack<char*> s3; s3.push(0);
}
- 최초의 class Stack을 Primary template이라고 한다.
- 특수화의 종류는 특수화와 부분 특수화가 있다.
- 부분 특수화는 타입 T에 대해 *(포인터) 같은 부분적인 것에 대한 특수화이고,
- 특수화는 T가 아닌 일반 타입(char, int)에 대한 특수화이다.
다양한 상황에 대한 특수화
template<typename T, typename U>
struct Test
{
static void foo() { std::cout << "T, U" << std::endl; }
};
template<typename T, typename U>
struct Test<T*, U>
{
static void foo() { std::cout << "T*, U" << std::endl; }
};
template<typename T, typename U>
struct Test<T*, U*>
{
static void foo() { std::cout << "T*, U*" << std::endl; }
};
template<typename T>
struct Test<T, T>
{
static void foo() { std::cout << "T, T" << std::endl; }
};
// 핵심임, 2개의 변수 모두 T타입이라면 위에 굳이 T, U 둘다 적을 필요 없음
template<typename T>
struct Test<int, T>
{
static void foo() { std::cout << "int, T" << std::endl; }
};
//항상 특수화가 우선됨
template<>
struct Test<int, int>
{
static void foo() { std::cout << "int, int" << std::endl; }
};
template<>
struct Test<int, short>
{
static void foo() { std::cout << "int, short" << std::endl; }
};
//중요하고 자주 쓰임
template<typename T, typename U, typename V> // 인자를 3개 받는 이유를 잘 알아둬야 함
struct Test<T, Test<U, V>>
{
static void foo() { std::cout << "T, Test<U, V>" << std::endl; }
};
int main()
{
Test<int, double>::foo();
Test<int*, double>::foo();
Test<int*, double*>::foo();
Test<int, int>::foo(); // <int, T>에도 걸리고 <T, T>에도 걸림 둘중 어떤걸 써야 할지 모름
Test<int, char>::foo();
Test<int, short>::foo();
Test<double, Test<char, short>>::foo();
}
- 부분 특수화보다 특수화가 항상 더 우선시 된다.
- Primary template의 템플릿 인자가 2개라면 사용자는 반드시 2개를 전달해야 함
- 하지만 부분 특수화에서 일부 인자가 필요 없다면 꼭 2개가 아니어도 됨 ex) <T, T> or <int, T> 등
- 단, 부분 특수화의 선언에는 2개 꼭 적어줘야 한다!
특수화 시 주의사항
- 부분특수화가 있을 때 타입이 결정되는 원리가 중요함
- int*에 대한 특수화라고 하더라도 내부에는 T가 int로 쓰인다는 것
#include <iostream>
#include <string>
template<typename T>
struct Test
{
static void foo() { std::cout << typeid(T).name() << std::endl; }
};
// 여기서 typeid를 이용하면 T*(int*)가 아니라 T(int)가 됨
template<typename T>
struct Test<T*>
{
static void foo() { std::cout << typeid(T).name() << std::endl; }
};
int main()
{
Test<int>::foo();
Test<int*>::foo();
}
int
int << int*가 아니라 int로 나온다
템플릿 인자에 초기값을 주는 경우
- 그건 Primary template에만 주면 되고, 부분 특수화에선 줄 필요 없음
- 안줘도 Primary쪽에서 세팅된 값으로 설정되어 있음
#include <iostream>
#include <string>
template<typename T, int N = 10>
struct Stack
{
T buff[N];
};
//부분 특수화에선 default 값은 빼줘야 함, 뺀다고 없는게 아니라 생략된 것
template<typename T, int N> struct Stack<T*, N>
{
T buff[N];
};
int main()
{
Stack<int, 10> s1;
Stack<int> s2;
Stack<int*> s3;
}
멤버 함수 특수화하는 경우
#include <iostream>
#include <string>
template<typename T> class Stack
{
public:
T pop() {}
//특정 멤버 함수만 특수화하는 경우
void push(T a);
};
template<typename T>
void Stack<T>::push(T a)
{
std::cout << "T" << std::endl;
}
//멤버 함수 특수화
//부분 특수화는 멤버 함수 하나는 안되고 클래스 전체를 대상으로 해야 함
template<>
void Stack<char*>::push(char* a)
{
std::cout << "char*" << std::endl;
}
int main()
{
Stack<int> s1; s1.push(0);
Stack<char*> s2; s2.push(0);
}
- 멤버 함수 하나만 부분 특수화하는 경우, 그 멤버 하나의 Primary를 밖으로 뺀 뒤에
- 그 Primary에 대한 특수화를 진행하면 됨
- 부분 특수화는 멤버 함수 하나는 안되고 클래스 전체를대상으로 해야 함
Template meta programming
- 컴파일 타임에 연산을 진행하는 프로그래밍 기법
- 핵심은 재귀를 사용하고, 재귀를 종료하기 위해 특수화를 사용한다는 것
template<int N>
struct factorial
{
enum { value = N * factorial<N - 1>::value };
};
template<>
struct factorial<1>
{
enum { value = 1 };
};
int main()
{
int n = factorial<5>::value;
// 5 * f<4>::value
// 4 * f<3>::value
// 3 * f<2>::value
// 2 * f<1>::value
// 1
//
std::cout << n << std::endl;
return 0;
}
- 템플릿이 풀리는 시간은 실행시간이 아닌 컴파일 시간
constexpr을 이용한 Template meta programming
#include <iostream>
#include <string>
// 여기 있는 일을 컴파일 시간에 해달라는 것
// 간단한 함수를 만들거면 constexpr을 붙여주면 성능향상에 이점이 있음
constexpr int add(int a, int b)
{
return a + b;
}
template<int N> struct check {};
int main()
{
//constexpr의 결과값은 constexpr 변수로 받음
constexpr int n = add(1, 2);
check<add(1, 2)> c;
int n1 = 1, n2 = 2;
return 0;
}
- constexpr 함수는 컴파일 타임에 계산할 수 있는 것들은 계산해달라는 것.
- VS의 경우 constexpr 함수의 결과를 지역변수로 반환받는 경우 constexpr을 앞에 붙여달라고 warning이 뜬다
'C++ > [ecourse] C++ Template' 카테고리의 다른 글
8. 가변인자 템플릿 (0) | 2022.10.06 |
---|---|
7. 템플릿 Type Traits (2) | 2022.09.30 |
5. 템플릿 기본 문법 - typename, template (0) | 2022.09.28 |
4. 템플릿 기본 문법 - 클래스 템플릿 (0) | 2022.09.27 |
3. 배열과 템플릿 (0) | 2022.09.26 |
댓글