1. c++에 존재하는 템플릿의 두 가지 종류는 무엇이며, 각각 어떻게 특수화하는가?
a) 클래스 템플릿과 함수 템플릿이 있으며 이 두 템플릿은 정확히 동일한 방식으로는 작동하지 않는데 가장 명백한 차이는 중복적재에 있다. 보통의 c++ 클래스를 중복적재 할 수 없으며 따라서 클래스 템플릿도 중복적재 할 수 없다. 반면 보통의 c++함수를 중복적재할 수 있는 것처럼 함수 템플릿은 중복적재할 수 있다.
//클래스 템플릿
template<class T> class X{/*...*/}; (a)
//중복적재된 두 함수 템플릿
template<class T>void f(T); (b)
template<class T> void f(int,T,double); (c)
이런 특수화되지 않은 템플릿들을 기본 템플릿이라고 부른다.
기본 템플릿은 특수화될 수 있다. 클래스 템플릿과 달리 함수 템플릿은 완전 특수화만 가능하고, 부분 특수화는 불가능하다. 그러나 함수 템플릿은 중복적재가 가능하며, 중복적재를 통해서 부분 특수화와 같은 효과를 얻을 수 있다.
//포인터 형식에 대한 (a)의 부분 특수화
template<class T> class X<T*> {/*...*/};
//int에 대한 (a)의 완전 특수화
template <> class X<int> {/*...*/};
//(b)와 (c)를 중복적재하는 개별적인 기본 템플릿
//(b)의 부분 특수화는 아니다. 함수 템플릿에는 부분 템플릿
template<class T> void f(T*); //(d)
//int에 대한 (b)의 완전 특수화
template<> void f<int>(int); //(e)
//(b),(c),(d)를 중복적재하는 보통의 함수
//(e)를 중복적재하지는 않는데, 이에 대해서는 잠시 후에 이야기한다.
void f(double); //(f)
-> 함수 템플릿은 부분적으로 특수화할 수 없는 대신, 중복적재가 가능함을 기억할 것, 개별적인 기본 함수 템플릿을 중복적재함으로써 부분 특수화의 효과를 얻을 수 있다.
bool b;
int i;
double d;
f(b); //(b)를 호출한다. T는 bool이 된다.
f(i,42,d); //(c)를 호출한다. T는 int가 된다.
f(&i); //(d)를 호출한다. T는 int가 된다.
f(i); //(e)를 호출한다.
f(d); //(f)를 호출한다.
template<class T> //(a) 기본 템플릿
void f(T);
template<class T> //(b) : (a)를 중복적재하는 기본 템플릿. 함수 템플릿은 부분 특수화가 불가능하다.
void f(T*); // 대신 중복적재를 사용한다.
template<> //(c):(b)의 명시적 특수화
void f<int>(int*);
int *p;
f(p); //(c)를 호출한다.
template<class T>
void f(T); //(a)
template<>
void f<int*>(int*); //(b)
template<class T>
void f(T*); //(c)
int *p;
f(p); //어떤 f가 호출될까? 정답은 (c)이다.
//중복적재 해소는 특수화들을 무시하며
//기반 함수 템플릿들에 대해서만 작동한다.
-> 특수화는 중복적재되지 않는다.
※중복적재 해소 과정에서 함수 템플릿 특수화는 참여하지 않음을 기억할 것. 특수화는 그 기본 템플릿이 선택된 후에만 쓰이며, 기본 템플릿을 선택하는 과정에서 기본 템플릿이 특수화들을 가지고 있는지의 여부는 고려되지 않는다.
★★ "매개변수가 int*일 때를 위한 특수화를 구체적으로 작성했으며 예제의 호출문에서 나온 매개변수도 정확히 int*이므로 당연히 int*를 위한 특수화가 호출되어야 하지 않을까?"리거 생각할 수도 있을 것이다. 그러나 그런 생각에는 한가지 실수가 숨어있다. 매개변수 형식들과 정확히 일치하는 함수가 호출되도록 하는게 원래의 목적이었다면, 애초에 템플릿 특수화가 아니라 보통의 평범한 함수를 사용했어야 한다.
특수화가 중복적재에 관여하지 않는 이유는 알고 보면 매우 간단하다. 어떤 특정한 템플릿에 대한 하나의 특수화를 작성했다고 해서 선택되는 템플릿이 어떤 식으로 변하는 것은 바람직하지 않다고 생각했다.
◆◆ 교훈1 : 기본 함수 템플릿을 커스텀화하려 한다면, 그리고 그 커스텀화가 중복적재 해소에 관여하게 하고 싶다면(또는 정확한 일치의 경우에 항상 그 버전이 선택되게 하고 싶다면), 그 버전을 특수화로 만들지 말고 보통의 함수로 만들 것.
따름 정리 : 어떤 한 함수 템플릿에 대해 중복적재들을 제고한다면, 특수화는 제공하지 말아야 한다.
◆◆교훈2: 특수화가 필요할만한 기본 함수 템플릿을 작성하는 경우, 그것을 특수화와 중복적재가 절대로 되지 않아야 하는 함수 템플릿으로 만들고, 그 함수 템플릿이 그와 동일한 서명을 가진 정적 함수를 담은 클래스 템플릿의 그 정적 함수를 호출하게 할 것. 사용자는 그 클래스를 얼마든지 특수화할 수 있으며, 그러한 특수화가 중복적재 해소의 결과에 영향을 미치지도 않는다.
a) 클래스 템플릿과 함수 템플릿이 있으며 이 두 템플릿은 정확히 동일한 방식으로는 작동하지 않는데 가장 명백한 차이는 중복적재에 있다. 보통의 c++ 클래스를 중복적재 할 수 없으며 따라서 클래스 템플릿도 중복적재 할 수 없다. 반면 보통의 c++함수를 중복적재할 수 있는 것처럼 함수 템플릿은 중복적재할 수 있다.
//클래스 템플릿
template<class T> class X{/*...*/}; (a)
//중복적재된 두 함수 템플릿
template<class T>void f(T); (b)
template<class T> void f(int,T,double); (c)
이런 특수화되지 않은 템플릿들을 기본 템플릿이라고 부른다.
기본 템플릿은 특수화될 수 있다. 클래스 템플릿과 달리 함수 템플릿은 완전 특수화만 가능하고, 부분 특수화는 불가능하다. 그러나 함수 템플릿은 중복적재가 가능하며, 중복적재를 통해서 부분 특수화와 같은 효과를 얻을 수 있다.
//포인터 형식에 대한 (a)의 부분 특수화
template<class T> class X<T*> {/*...*/};
//int에 대한 (a)의 완전 특수화
template <> class X<int> {/*...*/};
//(b)와 (c)를 중복적재하는 개별적인 기본 템플릿
//(b)의 부분 특수화는 아니다. 함수 템플릿에는 부분 템플릿
template<class T> void f(T*); //(d)
//int에 대한 (b)의 완전 특수화
template<> void f<int>(int); //(e)
//(b),(c),(d)를 중복적재하는 보통의 함수
//(e)를 중복적재하지는 않는데, 이에 대해서는 잠시 후에 이야기한다.
void f(double); //(f)
-> 함수 템플릿은 부분적으로 특수화할 수 없는 대신, 중복적재가 가능함을 기억할 것, 개별적인 기본 함수 템플릿을 중복적재함으로써 부분 특수화의 효과를 얻을 수 있다.
bool b;
int i;
double d;
f(b); //(b)를 호출한다. T는 bool이 된다.
f(i,42,d); //(c)를 호출한다. T는 int가 된다.
f(&i); //(d)를 호출한다. T는 int가 된다.
f(i); //(e)를 호출한다.
f(d); //(f)를 호출한다.
template<class T> //(a) 기본 템플릿
void f(T);
template<class T> //(b) : (a)를 중복적재하는 기본 템플릿. 함수 템플릿은 부분 특수화가 불가능하다.
void f(T*); // 대신 중복적재를 사용한다.
template<> //(c):(b)의 명시적 특수화
void f<int>(int*);
int *p;
f(p); //(c)를 호출한다.
template<class T>
void f(T); //(a)
template<>
void f<int*>(int*); //(b)
template<class T>
void f(T*); //(c)
int *p;
f(p); //어떤 f가 호출될까? 정답은 (c)이다.
//중복적재 해소는 특수화들을 무시하며
//기반 함수 템플릿들에 대해서만 작동한다.
-> 특수화는 중복적재되지 않는다.
※중복적재 해소 과정에서 함수 템플릿 특수화는 참여하지 않음을 기억할 것. 특수화는 그 기본 템플릿이 선택된 후에만 쓰이며, 기본 템플릿을 선택하는 과정에서 기본 템플릿이 특수화들을 가지고 있는지의 여부는 고려되지 않는다.
★★ "매개변수가 int*일 때를 위한 특수화를 구체적으로 작성했으며 예제의 호출문에서 나온 매개변수도 정확히 int*이므로 당연히 int*를 위한 특수화가 호출되어야 하지 않을까?"리거 생각할 수도 있을 것이다. 그러나 그런 생각에는 한가지 실수가 숨어있다. 매개변수 형식들과 정확히 일치하는 함수가 호출되도록 하는게 원래의 목적이었다면, 애초에 템플릿 특수화가 아니라 보통의 평범한 함수를 사용했어야 한다.
특수화가 중복적재에 관여하지 않는 이유는 알고 보면 매우 간단하다. 어떤 특정한 템플릿에 대한 하나의 특수화를 작성했다고 해서 선택되는 템플릿이 어떤 식으로 변하는 것은 바람직하지 않다고 생각했다.
◆◆ 교훈1 : 기본 함수 템플릿을 커스텀화하려 한다면, 그리고 그 커스텀화가 중복적재 해소에 관여하게 하고 싶다면(또는 정확한 일치의 경우에 항상 그 버전이 선택되게 하고 싶다면), 그 버전을 특수화로 만들지 말고 보통의 함수로 만들 것.
따름 정리 : 어떤 한 함수 템플릿에 대해 중복적재들을 제고한다면, 특수화는 제공하지 말아야 한다.
◆◆교훈2: 특수화가 필요할만한 기본 함수 템플릿을 작성하는 경우, 그것을 특수화와 중복적재가 절대로 되지 않아야 하는 함수 템플릿으로 만들고, 그 함수 템플릿이 그와 동일한 서명을 가진 정적 함수를 담은 클래스 템플릿의 그 정적 함수를 호출하게 할 것. 사용자는 그 클래스를 얼마든지 특수화할 수 있으며, 그러한 특수화가 중복적재 해소의 결과에 영향을 미치지도 않는다.