부분 특수화
부분 특수화 (Partial template specialization)
클래스 템플릿(그리고 C++14부터 변수 템플릿)을 템플릿 인자의 특정 범주에 대해 커스터마이즈할 수 있게 해주는 게 **부분 특수화(partial specialization)**예요. 전체 특수화가 "특정 인자 값 하나"를 딱 집어내는 것이라면, 부분 특수화는 "포인터 타입일 때", "첫 번째 인자가 int이고 세 번째가 5일 때" 같은 패턴 하나를 지정해요. 여전히 인자에 따라 여러 타입에 적용되기 때문에 '부분'이라는 이름이 붙어요.
출처: cppreference
본문
문법(Syntax)
template < parameter-list > class-key class-head-name < argument-list > declaration |
(1) | |
template < parameter-list > decl-specifier-seq declarator < argument-list > initializer (선택) |
(2) | (C++14부터) |
여기서 class-head-name은 이전에 선언된 클래스 템플릿의 이름을, declarator는 이전에 선언된 변수 템플릿(C++14부터)의 이름을 나타내요.
부분 특수화는 기본 템플릿(primary template)을 정의할 수 있는 모든 스코프에서 선언될 수 있어요. 단, 그 스코프는 기본 템플릿이 정의된 스코프와 다를 수 있어요(멤버 템플릿을 클래스 밖에서 특수화하는 경우처럼). 부분 특수화는 특수화되지 않은 템플릿 선언 뒤에 나타나야 해요.
예를 들어,
template<class T1, class T2, int I>
class A {}; // 기본 템플릿
template<class T, int I>
class A<T, T*, I> {}; // #1: T2가 T1에 대한 포인터인 부분 특수화
template<class T, class T2, int I>
class A<T*, T2, I> {}; // #2: T1이 포인터인 부분 특수화
template<class T>
class A<int, T*, 5> {}; // #3: T1이 int, I가 5, T2가 포인터인 부분 특수화
template<class X, class T, int I>
class A<X, T*, I> {}; // #4: T2가 포인터인 부분 특수화
표준 라이브러리의 부분 특수화 예로는 배열 타입을 위한 std::unique_ptr의 부분 특수화가 있어요.
인자 목록(The argument list)
부분 템플릿 특수화의 argument-list에는 다음 제한이 적용돼요.
- 인자 목록은 특수화되지 않은 템플릿의 인자 목록과 동일할 수 없어요(무언가를 특수화해야 함):
template<class T1, class T2, int I> class B {}; // 기본 템플릿
template<class X, class Y, int N> class B<X, Y, N> {}; // 에러
게다가 특수화는 기본 템플릿보다 더 특수화되어야 해요 (C++11부터):
template<int N, typename T1, typename... Ts> struct B;
template<typename... Ts> struct B<0, Ts...> {}; // 에러: 더 특수화되지 않음
-
인자 목록에 기본 인자(default arguments)가 나타날 수 없어요.
-
어떤 인자가 팩 확장이면, 그 인자는 목록의 마지막 인자여야 해요.
-
비타입 인자 표현식은, 매개변수가 비추론 문맥 밖에서 적어도 한 번 나타나기만 하면 템플릿 매개변수를 사용할 수 있어요(현재 clang과 gcc 12만 이 기능을 지원한다는 점에 주의):
template<int I, int J> struct A {};
template<int I> struct A<I + 5, I * 2> {}; // 에러: I를 추론할 수 없음
template<int I, int J, int K> struct B {};
template<int I> struct B<I, I * 2, 2> {}; // OK: 첫 번째 매개변수는 추론 가능
- 비타입 템플릿 인자는 특수화의 매개변수에 타입이 의존하는 템플릿 매개변수를 특수화할 수 없어요:
template<class T, T t> struct C {}; // 기본 템플릿
template<class T> struct C<T, 1>; // 에러: 인자 1의 타입은 T인데,
// 이는 매개변수 T에 의존함
template<int X, int(*array_ptr)[X]> class B {}; // 기본 템플릿
int array[5];
template<int X> class B<X, &array> {}; // 에러: &array의 타입은
// int(*)[X]인데, 이는 매개변수 X에 의존함
이름 룩업(Name lookup)
부분 템플릿 특수화는 이름 룩업으로 찾아지지 않아요. 기본 템플릿이 이름 룩업으로 발견될 때만 그 부분 특수화들이 고려돼요. 특히, 기본 템플릿을 보이게 하는 using 선언은 부분 특수화도 함께 보이게 해요:
namespace N
{
template<class T1, class T2> class Z {}; // 기본 템플릿
}
using N::Z; // 기본 템플릿을 가리킴
namespace N
{
template<class T> class Z<T, T*> {}; // 부분 특수화
}
Z<int, int*> z; // 이름 룩업은 N::Z(기본 템플릿)를 발견하고,
// 그 다음 T = int인 부분 특수화가 사용됨
부분 순서(Partial ordering)
클래스 또는(C++14부터) 변수 템플릿이 인스턴스화될 때 부분 특수화가 사용 가능하면, 컴파일러는 기본 템플릿을 쓸지 그 부분 특수화 중 하나를 쓸지 결정해야 해요.
- (코드 생략) 하나의 특수화가 일치하면 그것이 사용돼요.
- 둘 이상의 특수화가 일치하면, 부분 순서 규칙으로 어떤 특수화가 더 특수화된지 정해요. 가장 특수화된 특수화가 유일하면 그것을 사용하고, 유일하지 않으면 프로그램은 컴파일될 수 없어요.
- 어떤 특수화도 일치하지 않으면 기본 템플릿이 사용돼요.
// 위에 정의된 템플릿 A를 쓰면,
A<int, int, 1> a1; // 어떤 특수화도 일치하지 않음, 기본 템플릿 사용
A<int, int*, 1> a2; // 부분 특수화 #1 사용 (T = int, I = 1)
A<int, char*, 5> a3; // 부분 특수화 #3 사용, (T = char)
A<int, char*, 1> a4; // 부분 특수화 #4 사용, (X = int, T = char, I = 1)
A<int*, int*, 2> a5; // 에러: #2와 일치 (T = int, T2 = int*, I = 2)
// #4와도 일치 (X = int*, T = int, I = 2)
// 둘 중 어느 쪽도 다른 쪽보다 더 특수화되지 않음
비공식적으로 "A가 B보다 더 특수화됐다"는 것은 "A가 B가 받아들이는 타입들의 부분집합을 받아들인다"는 뜻이에요.
공식적으로, 부분 특수화 간 더-특수화됨 관계를 세우기 위해 각각을 다음과 같이 가상의 함수 템플릿으로 변환해요.
- 첫 번째 함수 템플릿은 첫 번째 부분 특수화와 같은 템플릿 매개변수를 갖고, 매개변수가 하나인 함수를 가져요. 그 매개변수의 타입은 첫 번째 부분 특수화의 모든 템플릿 인자를 갖는 클래스 템플릿 특수화예요.
그런 다음 함수 템플릿들을 함수 템플릿 오버로딩처럼 순위를 매겨요.
template<int I, int J, class T> struct X {}; // 기본 템플릿
template<int I, int J> struct X<I, J, int>
{
static const int s = 1;
}; // 부분 특수화 #1
// #1의 가상 함수 템플릿:
// template<int I, int J> void f(X<I, J, int>); #A
template<int I> struct X<I, I, int>
{
static const int s = 2;
}; // 부분 특수화 #2
// #2의 가상 함수 템플릿:
// template<int I> void f(X<I, I, int>); #B
int main()
{
X<2, 2, int> x; // #1과 #2 모두 일치
// 함수 템플릿의 부분 순서:
// #A는 #B로부터: void(X<I, J, int>)를 void(X<U1, U1, int>)로부터: 추론 OK
// #B는 #A로부터: void(X<I, I, int>)를 void(X<U1, U2, int>)로부터: 추론 실패
// #B가 더 특수화됨
...
}
부분 특수화의 멤버(Members of partial specializations)
부분 특수화의 멤버의 템플릿 매개변수 목록과 템플릿 인자 목록은 부분 특수화의 매개변수 목록과 인자 목록과 일치해야 해요.
기본 템플릿의 멤버처럼, 프로그램에서 사용될 때만 정의하면 돼요.
부분 특수화의 멤버들은 기본 템플릿의 멤버들과 관련이 없어요.
부분 특수화 멤버의 명시적(전체) 특수화는 기본 템플릿의 명시적 특수화와 같은 방식으로 선언해요.
template<class T, int I> // 기본 템플릿
struct A
{
void f(); // 멤버 선언
};
template<class T, int I>
void A<T, I>::f() {} // 기본 템플릿 멤버 정의
// 부분 특수화
template<class T>
struct A<T, 2>
{
void f();
void g();
void h();
};
// 부분 특수화의 멤버
template<class T>
void A<T, 2>::g() {}
// 부분 특수화 멤버의 명시적(전체) 특수화
template<>
void A<char, 2>::h() {}
int main()
{
A<char, 0> a0;
A<char, 2> a2;
a0.f(); // OK, 기본 템플릿의 멤버 정의를 사용
a2.g(); // OK, 부분 특수화의 멤버 정의를 사용
a2.h(); // OK, 부분 특수화 멤버의 완전 특수화된 정의를 사용
a2.f(); // 에러: 부분 특수화 A<T,2>에는 f()의 정의가 없음
// (기본 템플릿은 사용되지 않음)
...
}
기본 템플릿이 다른 클래스 템플릿의 멤버이면, 그 부분 특수화들은 둘러싼 클래스 템플릿의 멤버예요. 둘러싼 템플릿이 인스턴스화되면, 각 멤버 부분 특수화의 선언도 (템플릿의 다른 모든 멤버의 정의가 아닌 선언이 인스턴스화되는 것과 같은 방식으로) 인스턴스화돼요.
기본 멤버 템플릿이 둘러싼 클래스 템플릿의 주어진 (암시적) 특수화에 대해 명시적으로 (전체) 특수화되면, 멤버 템플릿의 부분 특수화들은 둘러싼 클래스 템플릿의 그 특수화에 대해 무시돼요.
멤버 템플릿의 부분 특수화가 둘러싼 클래스 템플릿의 주어진 (암시적) 특수화에 대해 명시적으로 특수화되면, 기본 멤버 템플릿과 그 다른 부분 특수화들은 둘러싼 클래스 템플릿의 그 특수화에 대해 여전히 고려돼요.
template<class T> struct A // 둘러싼 클래스 템플릿
{
template<class T2>
struct B {}; // 기본 멤버 템플릿
template<class T2>
struct B<T2*> {}; // 멤버 템플릿의 부분 특수화
};
template<>
template<class T2>
struct A<short>::B {}; // 기본 멤버 템플릿의 완전 특수화
// (부분 특수화는 무시됨)
A<char>::B<int*> abcip; // 부분 특수화 사용 (T2 = int)
A<short>::B<int*> absip; // 기본의 완전 특수화 사용 (부분 특수화 무시)
A<char>::B<int> abci; // 기본 사용
...
결함 보고(Defect reports)
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 727 | C++98 | 클래스 스코프에서 부분·전체 특수화가 허용되지 않음 | 모든 스코프에서 허용 |
| CWG 1315 | C++98 | 비타입 템플릿 인자에서 id-expression이 아닌 것은 템플릿 매개변수를 쓸 수 없었음 | 추론 가능하면 표현식도 OK |
| CWG 1495 | C++11 | 매개변수 팩이 관련될 때 명세가 불분명했음 | 특수화는 더 특수화되어야 함 |
| CWG 1711 | C++14 | 변수 템플릿 부분 특수화의 명세가 없었음 | 변수 템플릿 지원을 추가 |
| CWG 1819 | C++98 | 부분 특수화 정의에 허용되는 스코프가 불명확했음 | 기본 템플릿과 같은 스코프에서 선언 가능 |
| CWG 2330 | C++14 | 변수 템플릿에 대한 참조가 없었음 | 변수 템플릿 지원을 추가 |