명시적(전체) 템플릿 특수화

명시적(전체) 템플릿 특수화 (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): 일부 인자만 고정하는 특수화.
  • 템플릿 매개변수와 인자: 특수화가 실제로 고정하는 대상.