본문 바로가기
C++/[ecourse] C++ Template

6. 템플릿 특수화

by 헛둘이 2022. 9. 29.

본 글은 코드누리의 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

댓글