6 분 소요

개요

C++ 템플릿은 특정 타입에 대해 재정의한 템플릿 특수화, 템플릿 부분 특수화, 함수 템플릿 특수화, 함수 오버로딩을 이용하여 다형성을 지원하는데요,

일반화된 주 템플릿 버전과 여러 템플릿 특수화 버전 중에서 좀 더 특수화된 버전을 선택해서 실행합니다.(좀 더 특수화된 버전은 좀 더 적은 타입을 허용한다고 생각하시면 됩니다.)

템플릿 특수화

템플릿 특수화를 위해서는 다음 코드처럼 template<>을 이용하여 원하는 타입에 대해 재정의하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
class A {
public:
    int f() {return 1;} // #1    
};

template<>
class A<int> {
public:
    int f() {return 2;} // #2    
};

A<char> a;
A<int> b;

EXPECT_TRUE(a.f() == 1);
EXPECT_TRUE(b.f() == 2); // int에 특수화된 버전 호출

함수 템플릿 특수화

함수 템플릿의 경우도 template<>을 이용하여 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>
int Func(T val) {return 1;} // #1

template<>
int Func<int>(int val) {return 2;} // #2

// 혹은 추론되는 타입을 아예 빼서 정의할 수 있습니다.
template<>
int Func(double val) {return 3;} // #3


EXPECT_TRUE(Func('a') == 1);
EXPECT_TRUE(Func(10) == 2); // int에 특수화된 버전 호출
EXPECT_TRUE(Func(10.0) == 3); // double에 특수화된 버전 호출

멤버 함수 템플릿 및 중첩 클래스 템플릿 특수화

멤버 함수 템플릿의 경우도 template<>을 이용하여 정의합니다. 단, 멤버 함수 템플릿을 특수화 하면, 상위 템플릿도 특수화 하여야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<typename T>
class A {
public:
    template<typename U>
    class B {
    public:
        template<typename V>
        int f(V val) {return 1;} // #1
    };
};

template<> // 바깥쪽 주 템플릿도 특수화
template<> // 안쪽 중첩 클래스 템플릿 특수화
template<> // 멤버 함수 템플릿 특수화
int A<int>::B<int>::f<int>(int val) {return 2;} // #2

// (X) 컴파일 오류. 멤버 함수 템플릿을 특수화 하면, 상위 템플릿도 특수화 해야 합니다.
// template<typename T> 
// template<typename U> 
// template<> 
// int A<T>::B<U>::f<int>(int val) {return 2;} // #2

A<char>::B<int> a;
A<int>::B<int> b;

EXPECT_TRUE(a.f('a') == 1);
EXPECT_TRUE(a.f(10) == 1); 
EXPECT_TRUE(b.f('a') == 1); 
EXPECT_TRUE(b.f(10) == 2); // int에 특수화된 버전 호출

템플릿 부분 특수화

템플릿 부분 특수화는 기존 타입에서 일부분을 특수화한 경우입니다.

다음은 주 템플릿에서 포인터형만을 특수화한 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
class A { // 주 템플릿
public:
    int f() {return 1;} // #1. 
};

template<typename T>
class A<T*> { // 템플릿 부분 특수화 
public:
    int f() {return 2;} // #2. 
};

A<int> a;
A<int*> b;

EXPECT_TRUE(a.f() == 1);
EXPECT_TRUE(b.f() == 2); // 템플릿 부분 특수화 버전

템플릿 부분 특수화에서 선언과 정의 분리

템플릿 부분 특수화에서 선언과 정의를 분리하면, 템플릿 부분 특수화 정의 뿐 아니라 전체 특수화 정의도 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 주 템플릿
template<typename T, typename U> 
class A {
public:
    int f() {return 1;}
};
 
// 부분 특수화
template<typename T>
class A<T, int> {
public:
    int f();
};

// 주 템플릿 정의
template<typename T, typename U>
int A<T, U>::f() {return 1;}  

// 부분 특수화 정의
template<typename T>
int A<T, int>::f() {return 2;}
 
// 전체 특수화 정의도 가능
template<>
int A<char, int>::f() {return 3;}

A<int, char> a; // 주 템플릿
A<int, int> b;// U == int 인 부분 특수화
A<char, int> c; // 전체 특수화

EXPECT_TRUE(a.f() == 1);
EXPECT_TRUE(b.f() == 2);
EXPECT_TRUE(c.f() == 3);

템플릿 특수화 우선 순위

템플릿 특수화템플릿 부분 특수화를 혼용할 경우 좀 더 특수화된 버전 을 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename T>
class A { // 주 템플릿
public:
    int f() {return 1;} // #1. 
};

template<typename T>
class A<T*> { // 템플릿 부분 특수화 
public:
    int f() {return 2;} // #2. 
};

template<>
class A<int*> { // 템플릿 특수화 
public:
    int f() {return 3;} // #3. 
};  
        
A<int> a;
A<int*> b;
A<char*> c;

EXPECT_TRUE(a.f() == 1);
EXPECT_TRUE(b.f() == 3); // 템플릿 특수화 버전
EXPECT_TRUE(c.f() == 2); // 템플릿 부분 특수화 버전

함수 템플릿 오버로딩

함수 템플릿템플릿 부분 특수화를 지원하지 않으며, 대신 함수 템플릿 오버로딩을 지원합니다. 모양은 마치 템플릿 부분 특수화와 비슷합니다.

다음 코드에서 f(&a) 호출시 #1과 #2은 각각 다음처럼 함수 오버로딩 후보로 만들어 집니다.

  1. #1 : T == int* 라면, f<int*>(int*) 로 호출 가능합니다.
  2. #2 : T == int 라면, f<int>(int*) 로 호출 가능합니다.

상기 후보 목록에서 좀 더 적은 타입을 허용하는 함수 템플릿 오버로딩 버전은 #2이므로(포인터만 호출 가능), int f(T*) 가 호출됩니다. 좀 더 자세한 내용은 함수 템플릿 오버로딩 결정 규칙을 참고하세요.

1
2
3
4
5
6
7
8
9
template<typename T> 
int f(T) {return 1;} // #1

template<typename T> 
int f(T*) {return 2;} // #2. 함수 템플릿 오버로딩. 부분 특수화와 모양이 비슷합니다.

int a;
EXPECT_TRUE(f(a) == 1); // 주 함수 템플릿 버전이 실행됩니다. T == int, f<int>(int)
EXPECT_TRUE(f(&a) == 2); // 템플릿 오버로딩 버전이 실행됩니다. T == int, f<int>(int*)

함수 템플릿 오버로딩과 함수 템플릿 특수화의 혼용

함수 템플릿 오버로딩의 모양이 템플릿 부분 특수화와 유사하나 굳이 구별하여 함수 템플릿 오버로딩이라고 하는 건, 함수 선택 과정에서 차이가 있기 때문입니다.

즉, 먼저 함수 템플릿 오버로딩을 선택한 후 선택한 버전의 함수 템플릿 특수화 버전을 찾습니다. 상기의 방식 차이 때문에 함수 템플릿 오버로딩함수 템플릿 특수화가 혼용되면, 순서에 주의해야 합니다.

다음 코드에서 함수 템플릿 특수화 버전인 #3을 #1과 #2 사이에 기재 했습니다. #3 입장에서는 함수 템플릿이 #1 밖에 없으므로, #3은 #1의 함수 템플릿 특수화 버전입니다.

f(&a) 호출시 다음처럼 함수 오버로딩 후보가 만들어 집니다.

  1. #1 : T == int* 라면, f<int*>(int*) 로 호출 가능합니다.
  2. #2 : T == int 라면, f<int>(int*) 로 호출 가능합니다.

먼저 함수 템플릿 오버로딩 버전에서 적합한 것을 찾습니다. 즉, #1과 #2 중에서만 찾습니다. #1보다는 #2가 특수화되었으므로, #2가 선택됩니다. #2에는 함수 템플릿 특수화 버전이 별도로 없으므로 그냥 #2가 호출됩니다.

f(&a) 호출시 가장 적합한 것은 #3인 것 같지만, #3은 #1의 함수 템플릿 특수화 버전이다 보니 후보로 검토되지 조차 않습니다.

1
2
3
4
5
6
7
8
9
10
11
template<typename T> 
int f(T) {return 1;} // #1

template<> 
int f(int*) {return 3;} //#3. #1의 템플릿 특수화 버전. T== int*, int f<int*>(int*)

template<typename T> 
int f(T*) {return 2;} // #2. 함수 템플릿 오버로딩. 

int a;
EXPECT_TRUE(f(&a) == 2); // 템플릿 오버로딩 버전이 실행됩니다. T == int, f<int>(int*)

타입을 명시적으로 작성하면 하기와 같이 호출됩니다.

1
2
3
int a;
EXPECT_TRUE(f<int*>(&a) == 3); // #1 함수 템플릿이 선택되고, 이중 특수화 버전인 #3 이 호출됩니다.
EXPECT_TRUE(f<int>(&a) == 2); // #2 함수 템플릿이 호출됩니다.

이번에는 함수 템플릿 특수화 버전인 #3 의 순서를 바꿔, #2 다음에 정의해 봅시다. 이번에는 #3이 #1과 #2중 좀더 적합한 #2의 함수 템플릿 특수화 버전이 됩니다.

f(&a) 호출시 다음처럼 함수 오버로딩 후보가 만들어 집니다.

  1. #1 : T == int* 라면, f<int*>(int*) 로 호출 가능합니다.
  2. #2 : T == int 라면, f<int>(int*) 로 호출 가능합니다.

이전과 동일하게 #1보다는 #2가 특수화되었으므로, #2가 선택되고, #2에는 함수 템플릿 특수화 버전이 있으므로, int 함수 템플릿 특수화 버전인 #3(int f(int*))이 호출됩니다.

1
2
3
4
5
6
7
8
9
10
11
template<typename T> 
int f(T) {return 1;} // #1

template<typename T> 
int f(T*) {return 2;} // #2. 함수 템플릿 오버로딩. 

template<> 
int f(int*) {return 3;} //#3. #2의 템플릿 특수화 버전. T== int, int f<int>(int*)

int a;
EXPECT_TRUE(f(&a) == 3); // 템플릿 특수화 버전이 실행됩니다. T == int, f<int>(int*)

타입을 명시적으로 작성하면 하기와 같이 호출됩니다.

1
2
3
int a;
EXPECT_TRUE(f<int*>(&a) == 1); // #1 함수 템플릿이 선택됩니다.
EXPECT_TRUE(f<int>(&a) == 3); // #2 함수 템플릿이 선택되고, 이중 특수화 버전인 #3 이 호출됩니다.

함수 템플릿 특수화 버전은 이처럼 함수 템플릿 오버로딩 버전의 충분한 정보가 없다면, 엉뚱한 함수 템플릿을 특수화 할 수 있습니다. 따라서, 다음의 순서로 함수 템플릿 오버로딩 버전과 함수 템플릿 특수화 버전을 작성해야 합니다.

  1. 함수 템플릿 정의
  2. 함수 템플릿 오버로딩 버전 정의
  3. 함수 템플릿 특수화 버전 정의

댓글남기기