명시적(전체) 템플릿 특수화
명시적(전체) 템플릿 특수화 (Explicit (full) template specialization)
템플릿은 타입 가족을 찍어내는 틀이지만, 특정 템플릿 인자에 대해서는 다른 코드를 쓰고 싶을 때가 있어요. 그럴 때 그 인자 집합에 대한 템플릿 코드를 맞춤 수정하는 걸 **명시적(전체) 템플릿 특수화(explicit template specialization)**라고 불러요. 주어진 템플릿 인자 집합에 대해 템플릿 코드를 커스터마이즈할 수 있게 해줘요.
출처: cppreference
본문
문법 (Syntax)
template <> declaration
다음 중 어떤 것이든 완전히 특수화될 수 있어요.
- 함수 템플릿
- 클래스 템플릿
- 변수 템플릿 (C++14부터)
- 클래스 템플릿의 멤버 함수
- 클래스 템플릿의 정적 데이터 멤버
- 클래스 템플릿의 멤버 클래스
- 클래스 템플릿의 멤버 열거형
- 클래스나 클래스 템플릿의 멤버 클래스 템플릿
- 클래스나 클래스 템플릿의 멤버 함수 템플릿
- 클래스나 클래스 템플릿의 멤버 변수 템플릿 (C++14부터)
예를 들어, is_void를 특수화해 void에 대해서만 true_type에서 파생되게 할 수 있어요.
#include <type_traits>
template<typename T> // 기본 템플릿(primary template)
struct is_void : std::false_type {};
template<> // T = void에 대한 명시적 특수화
struct is_void<void> : std::true_type {};
int main()
{
static_assert(is_void<char>::value == false,
"void가 아닌 모든 타입 T에 대해, 클래스는 false_type에서 파생됨");
static_assert(is_void<void>::value == true,
"하지만 T가 void일 때는, 클래스는 true_type에서 파생됨");
}
세부 사항 (In detail)
명시적 특수화는 그 기본 템플릿이 정의될 수 있는 어떤 스코프에서든 선언될 수 있어요(기본 템플릿이 정의된 스코프와 다를 수 있어요. 예: 멤버 템플릿의 클래스 외 특수화). 명시적 특수화는 특수화되지 않은 템플릿 선언 뒤에 나타나야 해요.
namespace N
{
template<class T> // 기본 템플릿
class X { /*...*/ };
template<> // 같은 이름공간에서의 특수화
class X<int> { /*...*/ };
template<class T> // 기본 템플릿
class Y { /*...*/ };
template<> // double에 대한 특수화의 전방 선언
class Y<double>;
}
template<> // OK: 같은 이름공간에서의 특수화
class N::Y<double> { /*...*/ };
특수화는 암시적 인스턴스화를 일으킬 첫 사용 이전에 선언되어야 해요. 그런 사용이 일어나는 모든 번역 단위에서 말이에요.
class String {};
template<class T>
class Array { /*...*/ };
template<class T> // 기본 템플릿
void sort(Array<T>& v) { /*...*/ }
void f(Array<String>& v)
{
sort(v); // sort(Array<String>&)의 인스턴스화를 암시적으로 수행,
} // sort()의 기본 템플릿을 사용
template<> // ERROR: sort(Array<String>)의 명시적 특수화
void sort<String>(Array<String>& v); // 암시적 인스턴스화 이후
선언됐지만 정의되지 않은 템플릿 특수화는 다른 불완전 타입처럼 사용될 수 있어요(그것에 대한 포인터와 참조를 사용할 수 있음).
template<class T> // 기본 템플릿
class X;
template<> // 특수화 (선언됐지만 정의되지 않음)
class X<int>;
X<int>* p; // OK: 불완전 타입에 대한 포인터
X<int> x; // error: 불완전 타입의 객체
함수나 변수(C++14부터) 템플릿의 명시적 특수화가 inline/constexpr(C++11부터)/constinit/consteval(C++20부터)인지는 특수화 자신이 결정해요. 기본 템플릿이 그 지정자로 선언됐는지와 무관하게요. 마찬가지로 템플릿 선언에 나타나는 속성들은 그 템플릿의 명시적 특수화에 영향을 주지 않아요. (C++11부터)
template<class T>
void f(T) { /* ... */ }
template<>
inline void f<>(int) { /* ... */ } // OK, inline
template<class T>
inline T g(T) { /* ... */ }
template<>
int g<>(int) { /* ... */ } // OK, inline이 아님
template<typename>
[[noreturn]] void h([[maybe_unused]] int i);
template<> void h<int>(int i)
{
// [[noreturn]]은 효과가 없지만, [[maybe_unused]]는 있음
}
함수 템플릿의 명시적 특수화 (Explicit specializations of function templates)
함수 템플릿을 특수화할 때, 템플릿 인자 추론(template argument deduction)이 함수 인자로부터 제공할 수 있다면 그 템플릿 인자를 생략할 수 있어요.
template<class T>
class Array { /*...*/ };
template<class T> // 기본 템플릿
void sort(Array<T>& v);
template<> // T = int에 대한 특수화
void sort(Array<int>&);
// 이렇게 쓸 필요 없음
// template<> void sort<int>(Array<int>&);
특수화와 같은 이름, 같은 인자 목록을 가진 함수는 특수화가 아니에요(함수 템플릿의 템플릿 오버로딩 참조).
함수 템플릿, 멤버 함수 템플릿, 그리고 클래스가 암시적으로 인스턴스화될 때 클래스 템플릿의 멤버 함수의 명시적 특수화에는 기본 함수 인자를 지정할 수 없어요.
명시적 특수화는 friend 선언이 될 수 없어요.
특수화의 멤버들 (Members of specializations)
명시적으로 특수화된 클래스 템플릿의 멤버를 클래스 외부에서 정의할 때는 template<> 문법을 쓰지 않아요. 단, 멤버 클래스 템플릿으로 특수화되는 명시적으로 특수화된 멤버 클래스 템플릿의 멤버인 경우는 예외예요. 그 경우에는 클래스 템플릿으로 특수화되므로, 그렇지 않으면 그런 정의가 중첩 템플릿이 요구하는 template<parameters>로 시작해야 하기 때문이에요.
template<typename T>
struct A
{
struct B {}; // 멤버 클래스
template<class U> // 멤버 클래스 템플릿
struct C {};
};
template<> // 특수화
struct A<int>
{
void f(int); // 특수화의 멤버 함수
};
// 특수화의 멤버에는 template<>가 쓰이지 않음
void A<int>::f(int) { /* ... */ }
template<> // 멤버 클래스의 특수화
struct A<char>::B
{
void f();
};
// 특수화된 멤버 클래스의 멤버에도 template<>가 쓰이지 않음
void A<char>::B::f() { /* ... */ }
template<> // 멤버 클래스 템플릿의 특수화
template<class U>
struct A<char>::C
{
void f();
};
// 클래스 템플릿으로 특수화되는 명시적으로 특수화된
// 멤버 클래스 템플릿의 멤버를 정의할 때는 template<>가 쓰임
template<>
template<class U>
void A<char>::C<U>::f() { /* ... */ }
템플릿의 정적 데이터 멤버의 명시적 특수화는, 선언이 초기화를 포함하면 정의이고, 그렇지 않으면 선언이에요. 이 정의들은 기본 초기화에 중괄호를 써야 해요.
template<>
X Q<int>::x; // 정적 멤버의 선언
template<>
X Q<int>::x (); // error: 함수 선언
template<>
X Q<int>::x {}; // 기본 초기화된 정적 멤버의 정의
클래스 템플릿의 멤버나 멤버 템플릿은, 멤버나 멤버 템플릿이 클래스 템플릿 정의 안에 정의되어 있더라도, 클래스 템플릿의 주어진 암시적 인스턴스화에 대해 명시적으로 특수화될 수 있어요.
template<typename T>
struct A
{
void f(T); // 멤버, 기본 템플릿에서 선언됨
void h(T) {} // 멤버, 기본 템플릿에서 정의됨
template<class X1> // 멤버 템플릿
void g1(T, X1);
template<class X2> // 멤버 템플릿
void g2(T, X2);
};
// 멤버의 특수화
template<>
void A<int>::f(int);
// 클래스 내에서 정의됐어도 멤버 특수화는 OK
template<>
void A<int>::h(int) {}
// 클래스 외 멤버 템플릿 정의
template<class T>
template<class X1>
void A<T>::g1(T, X1) {}
// 멤버 템플릿 특수화
template<>
template<class X1>
void A<int>::g1(int, X1);
// 멤버 템플릿 특수화
template<>
template<>
void A<int>::g2<char>(int, char); // X2 = char에 대해
// 동일, 템플릿 인자 추론 사용 (X1 = char)
template<>
template<>
void A<int>::g1(int, char);
멤버나 멤버 템플릿은 여러 둘러싸는 클래스 템플릿 안에 중첩될 수 있어요. 그런 멤버의 명시적 특수화에서는, 명시적으로 특수화되는 각 둘러싸는 클래스 템플릿마다 하나씩 template<>가 있어요.
template<class T1>
struct A
{
template<class T2>
struct B
{
template<class T3>
void mf();
};
};
template<>
struct A<int>;
template<>
template<>
struct A<char>::B<double>;
template<>
template<>
template<>
void A<char>::B<char>::mf<double>();
이런 중첩 선언에서 일부 단계는 특수화되지 않은 채 남을 수 있어요(단, 둘러싸는 클래스가 특수화되지 않았다면 이름공간 스코프에서 클래스 멤버 템플릿을 특수화할 수는 없어요). 그런 각 단계에 대해서는, 그런 특수화들이 그 자체로 템플릿이기 때문에, 그 선언에 template<arguments>가 필요해요.
template<class T1>
class A
{
template<class T2>
class B
{
template<class T3> // 멤버 템플릿
void mf1(T3);
void mf2(); // 비템플릿 멤버
};
};
// 특수화
template<> // 특수화된 A에 대해
template<class X> // 특수화되지 않은 B에 대해
class A<int>::B
{
template<class T>
void mf1(T);
};
// 특수화
template<> // 특수화된 A에 대해
template<> // 특수화된 B에 대해
template<class T> // 특수화되지 않은 mf1에 대해
void A<int>::B<double>::mf1(T t) {}
// ERROR: B<double>이 특수화됐고 멤버 템플릿이므로, 그 둘러싸는 A도
// 특수화되어야 함
template<class Y>
template<>
void A<Y>::B<double>::mf2() {}
결함 보고 (Defect reports)
다음의 동작 변경 결함 보고가 이전에 공개된 C++ 표준들에 소급 적용됐어요.
| DR | 적용 | 공개된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 531 | C++98 | 이름공간 스코프에서 명시적 특수화의 멤버를 정의하는 문법이 명시되지 않았음 | 명시함 |
| CWG 727 | C++98 | 클래스 스코프에서 부분·전체 특수화가 허용되지 않았음 | 어떤 스코프에서든 허용 |
| CWG 730 | C++98 | 비템플릿 클래스의 멤버 템플릿을 완전히 특수화할 수 없었음 | 허용 |
| CWG 2478 | C++20 | 기본 템플릿의 constinit과 consteval이 명시적 특수화로 이어지는지가 불명확했음 | 이어지지 않음 |
| CWG 2604 | C++11 | 기본 템플릿의 속성이 명시적 특수화로 이어지는지가 불명확했음 | 이어지지 않음 |
같이 보기 (See also)
- 템플릿
- 클래스 템플릿
- 함수 템플릿
- 부분 특수화 (Partial specialization)
더 알아보기
- 템플릿: 특수화의 대상이 되는 타입·함수 가족 정의.
- 템플릿 특수화/부분 특수화 (Partial specialization): 일부 인자만 고정하는 특수화.
- 템플릿 매개변수와 인자: 특수화가 실제로 고정하는 대상.