본문 바로가기

템플릿 특수화

일반 템플릿이 맞지 않는 타입에 명시적·부분 특수화를 제공하고 중복과 유지보수 비용을 고려해 적용합니다.

템플릿은 대부분의 경우 매우 유용하지만, 때로는 특정 타입에 대해서는 템플릿의 일반적인 구현이 적절하지 않거나, 더 효율적인 특별한 동작을 제공하고 싶은 경우가 발생합니다.

이럴 때 사용하는 것이 바로 템플릿 특수화(Template Specialization)입니다.

이번 장에서는 템플릿 특수화의 개념과 사용법, 그리고 부분 특수화(Partial Specialization)와 명시적 특수화(Explicit Specialization 또는 Full Specialization)의 차이를 정리합니다.

C++ · SPECIALIZATION SELECTION

정확한 특수화 → 맞는 부분 패턴 → 기본 템플릿

본문 MyValue와 Pair 예제의 인수 조합을 비교합니다. 타입 이름뿐 아니라 어떤 정의를 선택하는지와 그 근거까지 함께 읽습니다.

정확한 완전 특수화와 부분 패턴 및 기본 정의를 조건으로 선택한다8노드7간선. 인수확정→도달가능한정확한완전특수화조건. 예는완전정의 terminal;아니요는옆우회로부분패턴조건. 일치0개는기본정의 terminal;1개이상은우회로유일한최적부분패턴조건. 예는부분정의 terminal,아니요는모호성오류 terminal. 완전·기본·부분·오류결과사이에화살표가없고모바일에서도예/없음/0개/1개이상/아니요를보존한다. 이그림은클래스템플릿선택모델이며함수오버로드후보선택모델이아니다.클래스 인수타입·값 조합 확정완전 특수화 있음?정확한 조합이 도달 가능완전 특수화 사용그 조합의 별도 정의맞는 부분 패턴?전체 인수 관계로 검사기본 템플릿 사용일치하는 부분 패턴 없음하나로 정해짐?1개 또는 유일한 최적부분 특수화 사용선택한 패턴의 정의모호성 오류우열을 정할 수 없음예없음0개1개 이상예아니요최종 노드들 사이 간선 없음선언 순서로 승자를 고르지 않음
정확한 완전 특수화와 부분 패턴 및 기본 정의를 조건으로 선택한다8노드7간선. 인수확정→도달가능한정확한완전특수화조건. 예는완전정의 terminal;아니요는옆우회로부분패턴조건. 일치0개는기본정의 terminal;1개이상은우회로유일한최적부분패턴조건. 예는부분정의 terminal,아니요는모호성오류 terminal. 완전·기본·부분·오류결과사이에화살표가없고모바일에서도예/없음/0개/1개이상/아니요를보존한다. 이그림은클래스템플릿선택모델이며함수오버로드후보선택모델이아니다.클래스 인수타입·값 조합 확정완전 특수화 있음?정확한 조합이 도달 가능완전 특수화 사용그 조합의 별도 정의맞는 부분 패턴?전체 인수 관계로 검사기본 템플릿 사용일치하는 부분 패턴 없음하나로 정해짐?1개 또는 유일한 최적부분 특수화 사용선택한 패턴의 정의모호성 오류우열을 정할 수 없음예없음0개1개 이상예아니요최종 노드들 사이 간선 없음선언 순서로 승자를 고르지 않음
본문에 정의된 템플릿으로 선택 결과 확인
사용 타입선택하는 정의선택 근거
MyValue<int>기본 MyValue<T>일치하는 명시적 특수화가 없습니다.
MyValue<bool>MyValue<bool> 명시적 특수화정확한 bool 조합의 정의가 있습니다.
MyValue<const char*>MyValue<const char*> 명시적 특수화문자열을 std::string 멤버로 관리하는 별도 정의입니다.
Pair<double, char>기본 Pair<T1, T2>본문의 두 부분 특수화 패턴에 모두 맞지 않습니다.
Pair<std::string, int>Pair<T, int> 부분 특수화두 번째 인수가 int입니다.
Pair<int, int*>Pair<T, T*> 부분 특수화두 번째 인수는 첫 번째 타입의 포인터입니다.

MyValue<int>

선택: 기본 MyValue<T>

근거: 일치하는 명시적 특수화가 없습니다.

MyValue<bool>

선택: MyValue<bool> 명시적 특수화

근거: 정확한 bool 조합의 정의가 있습니다.

MyValue<const char*>

선택: MyValue<const char*> 명시적 특수화

근거: 문자열을 std::string 멤버로 관리하는 별도 정의입니다.

Pair<double, char>

선택: 기본 Pair<T1, T2>

근거: 본문의 두 부분 특수화 패턴에 모두 맞지 않습니다.

Pair<std::string, int>

선택: Pair<T, int> 부분 특수화

근거: 두 번째 인수가 int입니다.

Pair<int, int*>

선택: Pair<T, T*> 부분 특수화

근거: 두 번째 인수는 첫 번째 타입의 포인터입니다.

패턴이 여러 개 일치하면: 부분 순서화로 유일한 최적 특수화를 정할 수 있어야 합니다. 우열이 없는 여러 패턴이 맞으면 모호성 오류가 납니다.


템플릿 특수화의 필요성

템플릿은 모든 타입에 대해 일반적인 방식으로 동작하도록 설계됩니다.

하지만 특정 타입의 특성 때문에 일반적인 템플릿 구현이 문제가 되거나, 혹은 더 나은 성능을 낼 수 있는 특별한 방법이 있을 수 있습니다.

예시
  • std::string과 같은 문자열 타입은 일반적인 char 배열과 다르게 동작합니다. std::string의 복사는 문자열 전체를 복사하는 깊은 복사를 의미할 수 있습니다.
  • bool 타입은 메모리 효율성을 위해 특별하게 처리되는 경우가 많습니다. (예: std::vector<bool>)
  • char* (C-스타일 문자열)와 같은 포인터 타입은 일반적인 포인터와는 다른 비교나 복사 의미를 가질 수 있습니다.

이러한 경우 특정 타입에 대해서만 템플릿의 동작을 재정의하는 것이 필요하며, 이것이 템플릿 특수화의 목적입니다.


함수 템플릿의 명시적 특수화

함수 템플릿은 특정 타입에 대해 완전히 다른 구현을 제공할 수 있습니다.

이를 함수 템플릿의 명시적 특수화라고 합니다. 앞 절의 타입 추론·오버로딩에 더해, 선택한 템플릿의 특정 인수 조합에 어떤 정의를 사용할지를 다룹니다. 아래의 한글 이름을 쓴 형식은 설명용이고 mySwap 예제는 별도 파일 하나로 실행하는 프로그램입니다.

함수 템플릿 특수화 형식
template <> // 템플릿 매개변수 리스트는 비워둡니다.
반환타입 함수이름<특수화할_타입>(매개변수) {
    // 특수화된 타입에 대한 특별한 구현
}

std::swap은 해당 오버로드의 요구 조건을 만족하는 타입에 사용할 수 있습니다. 아래 mySwap은 특정 타입에 대한 특수화를 어떻게 하는지 보여주기 위한 예시입니다.

예를 들어 const char* (C-스타일 문자열)은 문자열 리터럴을 가리키는 경우가 많기 때문에, 소유권이 없는 포인터를 delete[] 하면 정의되지 않은 동작(UB)이 됩니다.

따라서 특수화 예시는 포인터 값만 안전하게 교환하는 방식으로 작성해야 합니다.

swap 함수 템플릿 특수화 (과거의 예시, 현재는 스마트 스왑이 더 좋음)
#include <iostream>
#include <string>
#include <typeinfo>
#include <cstring> // C-스타일 문자열 함수를 위해

// 1. 일반 함수 템플릿 정의
template <typename T>
void mySwap(T& a, T& b) {
    std::cout << "일반 템플릿 mySwap() 호출: ";
    T temp = a;
    a = b;
    b = temp;
}

// 2. int 타입에 대한 명시적 특수화
// template <> void mySwap<int>(int& a, int& b) {
//     std::cout << "int 명시적 특수화 mySwap() 호출: ";
//     int temp = a;
//     a = b;
//     b = temp;
// }
// 일반적으로 기본 타입에 대한 특수화는 잘 하지 않습니다.
// 성능상 이득이 없거나 더 나빠질 수 있기 때문입니다.

// 3. const char* 타입에 대한 명시적 특수화
// 문자열 소유권은 이 함수가 갖지 않으므로, 포인터 값만 교환
template <>
void mySwap<const char*>(const char*& a, const char*& b) {
    std::cout << "const char* 명시적 특수화 mySwap() 호출 (포인터 교환): ";
    const char* temp = a;
    a = b;
    b = temp;
}

int main() {
    int i1 = 10, i2 = 20;
    mySwap(i1, i2); // int 일반 템플릿 (또는 int 특수화) 호출
    std::cout << "i1: " << i1 << ", i2: " << i2 << std::endl; // 출력: i1: 20, i2: 10

    double d1 = 1.1, d2 = 2.2;
    mySwap(d1, d2); // double 일반 템플릿 호출
    std::cout << "d1: " << d1 << ", d2: " << d2 << std::endl; // 출력: d1: 2.2, d2: 1.1

    const char* s1 = "Hello";
    const char* s2 = "World";
    mySwap(s1, s2); // const char* 명시적 특수화 호출
    // s1과 s2가 "가리키는 대상"이 바뀜 (문자열 리터럴 메모리를 해제하지 않음)
    std::cout << "s1: " << s1 << ", s2: " << s2 << std::endl; // 출력: s1: World, s2: Hello

    // Note: C-스타일 문자열은 소유권 규칙이 까다롭습니다.
    // "내용 자체"를 교환/복사해야 한다면 const char* 대신 std::string을 사용하는 것이 안전합니다.

    std::string str1 = "Apple";
    std::string str2 = "Banana";
    mySwap(str1, str2); // std::string 일반 템플릿 호출 (std::string 자체는 깊은 복사 지원)
    std::cout << "str1: " << str1 << ", str2: " << str2 << std::endl; // 출력: str1: Banana, str2: Apple

    return 0;
}

원 코드의 int 특수화는 주석으로 닫혀 있으므로 현재 실행에서는 int·double·std::string 모두 일반 mySwap을 사용합니다. const char* 호출만 명시적 특수화의 안내가 나옵니다. 결과 쌍은 20·10, 2.2·1.1, World·Hello, Banana·Apple 순서입니다. 마지막 두 경우 모두 출력할 문자열이 바뀌지만 포인터 특수화는 포인터 두 개를 교환하고, std::string 일반 템플릿은 값의 복사·대입을 수행합니다. 이 const char* 특수화도 일반 템플릿과 같은 포인터 교환 알고리즘이므로 특수화를 적었다는 이유만으로 성능 개선을 주장하지 않습니다.

함수 템플릿 특수화의 주의사항
  • 함수 템플릿 특수화는 오버로딩과 다릅니다. 명시적 특수화는 독립적인 오버로드 후보가 아닙니다. 먼저 기본 함수 템플릿을 선택하고 해당 인수 조합의 명시적 특수화가 있으면 그 정의를 사용합니다.
  • 일반적으로 함수 템플릿 특수화보다는 함수 오버로딩을 선호합니다. 특정 타입에 대한 동작이 너무 달라 템플릿의 원래 의도에서 벗어나는 경우에만 특수화를 고려합니다.
  • 후보 참여 조건에는 C++11의 std::enable_if를 이용한 SFINAE나 C++20 제약을, 선택된 함수 본문의 컴파일 시간 분기에는 C++17의 if constexpr를 검토할 수 있습니다. if constexpr 자체는 SFINAE가 아닙니다.

C++ · OVERLOAD OR SPECIALIZATION

후보 추가, 본문 분기, 특정 구현 대체를 구분한다

함수 템플릿에는 partial specialization이 없다. 호출 후보를 추가할지 primary의 한 specialization을 대체할지 구분한다.

동작 차이를 표현할 세 설계 방향은 서로 독립이며 함께 쓸 수 있다원desktop4노드3간선fanout과같다. 바꾸려는타입계약에서함수오버로드,후보조건/본문분기,템플릿특수화세방향으로향한다. 선택지사이에시간간선이나우선순위간선은없고함께사용할수있다. SFINAE는후보조건관련규칙,if constexpr는선택된본문의분기라는서로다른단계를같은설계선택지안에서도구분한다.바꾸려는 계약특정 타입의 동작함수 오버로드별도 호출 후보 추가조건과 본문 분기SFINAE / if constexpr서로 다른 적용 단계템플릿 특수화선택 조합의 정의 교체세 선택은 함께 사용할 수 있음화살표는 설계 조건의 분기
함수 템플릿 예외는 overload를 먼저 검토한다 함수 동작만 다른 경우 overload나 if constexpr를 검토하고 정확한 primary specialization 대체에만 명시적 특수화를 사용한다. 차이가 어디에 있는가?호출 계약과 구조를 구분overload인자 형태·변환 규칙이 다름if constexpr작은 compile-time 분기명시 특수화정확한 한 타입만 대체
  1. 호출 형태가 다름

    overload로 후보를 명시한다.

  2. 작은 타입 분기

    if constexpr가 더 가까이 읽히는지 본다.

  3. 정확한 한 타입

    primary specialization 대체가 필요할 때만 명시 특수화한다.

명시적 함수 특수화는 독립 overload 후보가 아니라 선택된 primary specialization의 구현 대체다.


클래스 템플릿의 명시적 특수화

클래스 템플릿도 특정 타입에 대해 완전히 다른 구현을 제공할 수 있습니다.

이를 클래스 템플릿의 명시적 특수화라고 합니다.

클래스 템플릿 특수화 형식
template <>
class 클래스이름<특수화할_타입> {
    // 특수화된 타입에 대한 특별한 멤버 변수 및 멤버 함수 구현
};

일반적으로 MyValue 템플릿은 어떤 타입이든 저장하지만, bool 타입에 대해서는 다른 저장·표시 방식을 적용해 봅시다. 이 예제의 int 저장은 메모리 절약 예제가 아닙니다. 비트를 압축하는 std::vector<bool>과 구별합니다.

MyValue 클래스 템플릿 특수화 예시
#include <iostream>
#include <string>
#include <typeinfo>

// 1. 일반 클래스 템플릿 정의
template <typename T>
class MyValue {
private:
    T value;
public:
    MyValue(T val) : value(val) {
        std::cout << "일반 MyValue<" << typeid(T).name() << "> 객체 생성: " << value << std::endl;
    }
    T getValue() const { return value; }
    void setValue(T val) { this->value = val; }
    void display() const {
        std::cout << "일반 MyValue 값: " << value << std::endl;
    }
};

// 2. bool 타입에 대한 명시적 특수화
template <>
class MyValue<bool> {
private:
    // 값의 표현 방식을 바꾸는 예시이며 메모리 절약을 보장하지 않음
    // 여기서는 간단히 int로 저장하여 0/1로 표현
    int internalValue;
public:
    MyValue(bool val) : internalValue(val ? 1 : 0) {
        std::cout << "bool 명시적 특수화 MyValue<bool> 객체 생성: " << (val ? "true" : "false") << std::endl;
    }
    bool getValue() const { return (internalValue == 1); }
    void setValue(bool val) { this->internalValue = (val ? 1 : 0); }
    void display() const {
        std::cout << "bool 특수화 MyValue 값: " << (getValue() ? "True" : "False") << std::endl;
    }
};

// 3. const char* 타입에 대한 명시적 특수화
// 내부 저장은 std::string으로 관리하여 수동 메모리 관리 위험을 제거
template <>
class MyValue<const char*> {
private:
    std::string str;
public:
    explicit MyValue(const char* s) {
        std::cout << "const char* 명시적 특수화 MyValue<const char*> 객체 생성.\n";
        str = (s ? s : "");
    }
    const char* getValue() const { return str.c_str(); }
    void setValue(const char* s) {
        str = (s ? s : "");
    }
    void display() const {
        std::cout << "const char* 특수화 MyValue 값: " << str << std::endl;
    }
};

int main() {
    MyValue<int> intVal(123);           // 일반 템플릿 호출
    intVal.display();

    MyValue<double> doubleVal(45.67);   // 일반 템플릿 호출
    doubleVal.display();

    MyValue<bool> boolVal(true);        // bool 특수화 호출
    boolVal.display();
    boolVal.setValue(false);
    boolVal.display();

    MyValue<const char*> charPtrVal("Hello C++"); // const char* 특수화 호출
    charPtrVal.display();
    charPtrVal.setValue("World");
    charPtrVal.display(); // 출력: const char* 특수화 MyValue 값: World

    return 0;
}

위 예시에서 MyValue<bool>과 MyValue<const char*>은 일반 템플릿 MyValue<T>와는 완전히 다른 방식으로 데이터를 저장하고 처리합니다.

bool은 내부적으로 int로 표현되었고, const char* 특수화는 내부적으로 std::string을 사용해 수동 메모리 해제 없이 안전하게 문자열을 관리합니다. 생성할 때 입력 문자열을 내부 값으로 복사하지만 getValue의 반환값은 그 문자열을 가리키는 비소유 포인터입니다. 이 포인터를 delete하지 않으며, setValue로 문자열을 변경하거나 객체가 파괴된 뒤에도 보관한 포인터를 계속 쓸 수 있다고 가정하지 않습니다. 변경 뒤에는 getValue를 다시 호출하고, 독립적으로 오래 보관할 값이 필요하면 std::string으로 복사하세요.

스스로 확인하기 위 MyValue 정의는 유지하고 별도 main에서 아래를 실행하기 전에, 첫 display의 문자열과 마지막 display의 문자열을 예측하세요. input과 내부 str이 같은 배열이라고 가정하지 마세요.

MyValue 정의 아래에 넣을 교체 main
int main() {
    char input[] = "Sun";
    MyValue<const char*> word(input);
    input[0] = 'F';
    word.display();
    word.setValue(nullptr);
    word.display();
}
복사한 값과 빌린 포인터 풀이

입력 배열은 Fun으로 바뀌지만 word는 생성 시 복사한 Sun을 출력합니다. nullptr를 setValue에 주면 이 특수화의 조건식이 빈 문자열을 저장하므로 마지막 display의 값 부분은 비어 있습니다. 입력 포인터 자체를 멤버로 저장하는 구현과 다릅니다. getValue에서 얻은 주소를 유지한 채 setValue 후 역참조하는 실험은 하지 않습니다.


클래스 템플릿의 부분 특수화

클래스 템플릿은 모든 템플릿 매개변수를 특수화하지 않고, 일부만 특수화하는 것도 가능합니다.

이를 부분 특수화(Partial Specialization)라고 합니다.

함수 템플릿은 부분 특수화를 지원하지 않습니다.

클래스 템플릿 부분 특수화 형식
template <typename T> // 하나의 매개변수 T만 남겨둠
class 클래스이름<T, 다른_타입> { // 두 번째 매개변수를 특정 타입으로 고정
    // T 타입에 대한 부분 특수화된 구현
};

template <typename T>
class 클래스이름<T*> { // T 타입 포인터에 대한 부분 특수화
    // T* 타입에 대한 부분 특수화된 구현
};

Pair<T1, T2> 템플릿에서 두 번째 타입이 int인 경우와 첫 번째 타입의 포인터인 경우에 대해 부분 특수화를 해 봅시다. 부분 특수화는 단순히 일부 인수를 고정하는 경우뿐 아니라 T와 T*처럼 인수 사이의 관계를 지정할 수도 있습니다.

Pair 클래스 템플릿 부분 특수화 예시
#include <iostream>
#include <string>
#include <typeinfo>

// 1. 일반 클래스 템플릿 정의 (Pair.h에서 가져옴)
template <typename T1, typename T2>
class Pair {
private:
    T1 first;
    T2 second;
public:
    Pair(T1 f, T2 s) : first(f), second(s) {
        std::cout << "일반 Pair<" << typeid(T1).name() << ", " << typeid(T2).name() << "> 객체 생성.\n";
    }
    void display() const {
        std::cout << "일반 Pair: First=" << first << ", Second=" << second << std::endl;
    }
};

// 2. Pair<T, int> 에 대한 부분 특수화 (두 번째 타입이 int인 경우)
template <typename T>
class Pair<T, int> { // 두 번째 템플릿 매개변수를 int로 고정
private:
    T first;
    int second; // int 타입
public:
    Pair(T f, int s) : first(f), second(s) {
        std::cout << "부분 특수화 Pair<" << typeid(T).name() << ", int> 객체 생성.\n";
    }
    void display() const {
        std::cout << "Pair<T, int>: First=" << first << ", Second=" << second << " (int 특수화)\n";
    }
};

// 3. Pair<T, T*> 에 대한 부분 특수화 (두 번째 타입이 첫 번째 타입의 포인터인 경우)
template <typename T>
class Pair<T, T*> { // 두 번째 템플릿 매개변수를 T* (T의 포인터)로 고정
private:
    T value;
    T* ptr;
public:
    Pair(T v, T* p) : value(v), ptr(p) {
        std::cout << "부분 특수화 Pair<" << typeid(T).name() << ", " << typeid(T*).name() << "> (포인터) 객체 생성.\n";
    }
    void display() const {
        std::cout << "Pair<T, T*>: Value=" << value << ", Ptr=" << (ptr ? *ptr : T{}) << " (포인터 특수화)\n";
    }
};

int main() {
    Pair<double, char> p1(3.14, 'A');  // 일반 Pair 템플릿 사용
    p1.display(); // 출력: 일반 Pair: First=3.14, Second=A

    Pair<std::string, int> p2("Test", 123); // Pair<T, int> 부분 특수화 사용
    p2.display(); // 출력: Pair<T, int>: First=Test, Second=123 (int 특수화)

    int val = 100;
    Pair<int, int*> p3(50, &val); // Pair<T, T*> 부분 특수화 사용
    p3.display(); // 출력: Pair<T, T*>: Value=50, Ptr=100 (포인터 특수화)

    return 0;
}

컴파일러는 객체 생성 시점에 사용 가능한 템플릿 중에서 가장 특수화된 버전을 선택합니다.

정확한 인수 조합의 명시적 특수화가 있으면 그것을 사용합니다. 그렇지 않으면 일치하는 부분 특수화 중 부분 순서화로 하나를 선택하고, 일치하는 것이 없을 때 기본 템플릿을 사용합니다. 여러 패턴 중 우열을 정할 수 없으면 오류입니다.

스스로 확인하기 위 Pair 정의 세 개에 대해 Pair<int, int>, Pair<double, double*>, Pair<int, double*>가 각각 어느 정의를 사용하는지 결정하세요. 마지막 조합은 두 번째 인수가 포인터라는 이유만으로 T·T* 패턴에 맞을까요? 이어 원 main의 val을 200으로 바꾸고 p3를 다시 display하면 Value와 Ptr는 각각 무엇일까요?

패턴 일치와 저장 방식 풀이

순서대로 Pair<T, int>, Pair<T, T*>, 일반 Pair<T1, T2>입니다. 마지막 조합은 첫 인수에서 T=int, 두 번째에서 T=double을 동시에 요구하므로 T·T* 패턴에 맞지 않습니다. 원 p3에는 value 값 50이 복사되어 있고 ptr은 val을 빌려 가리키므로, val을 200으로 바꾸면 Value=50, Ptr=200입니다. 포인터가 가리키는 객체는 display할 때까지 살아 있어야 하며, 이 Pair가 val의 수명을 소유하거나 늘리는 것은 아닙니다. null 포인터를 주면 원 구현의 조건식이 T{} 값을 표시하므로 역참조하지 않습니다.

예시 다음은 타입 선택만 확인하는 별도 프로그램입니다. 두 부분 특수화가 각각 한 자리를 고정했으므로 Choice<int, int>에서는 둘 다 맞으며 어느 한쪽이 더 구체적이지 않습니다.

부분 특수화가 모호한 컴파일 실패 예
template <typename A, typename B>
struct Choice {};

template <typename T>
struct Choice<T, int> {};

template <typename U>
struct Choice<int, U> {};

int main() {
    Choice<int, int> ambiguous;
}

이 프로그램은 컴파일 오류가 예상되며 실행 출력은 없습니다. Choice<double, int>로 바꾸면 첫 번째 부분 특수화만 일치해 컴파일됩니다. 두 부분 특수화 아래, main 앞에 template <> struct Choice<int, int> {};를 추가하면 정확한 조합의 완전 특수화를 제공한 별도 변형도 컴파일됩니다. “특수화가 여러 개 있으면 마지막 선언이 이긴다”는 규칙은 아닙니다.


템플릿 특수화 사용 시 고려사항

  • 남용 금지: 템플릿 특수화는 표현력이 큰 기능이지만, 너무 많이 사용하면 코드의 복잡성을 증가시키고 유지보수를 어렵게 만듭니다. 일반적인 템플릿으로 해결할 수 있다면 특수화보다는 일반적인 구현을 선호하는 것이 좋습니다.
  • 오버로딩 vs 특수화: 함수 템플릿의 경우, 특정 타입에 대해 다른 동작을 제공할 때 함수 오버로딩과 특수화를 모두 사용할 수 있습니다. 일반적으로 함수 오버로딩이 더 선호됩니다. 특수화는 템플릿의 기본 동작을 대체하는 것이고, 오버로딩은 단순히 추가적인 함수를 제공하는 것으로 이해할 수 있습니다.
  • 정의 배치: 부분 특수화의 정의는 인스턴스화 지점에서 보여야 합니다. 완전 특수화된 함수는 헤더에 선언하고 한 소스 파일에서 정의하거나, 헤더 정의에 inline을 사용할 수 있습니다. 여러 번역 단위에 비-inline 함수 정의를 복제하면 ODR을 위반합니다. 완전 특수화 클래스도 일반 클래스처럼 멤버 함수의 선언과 정의를 분리할 수 있습니다.

특수화는 예외를 표현하는 도구이므로 “일반 템플릿으로 충분한가?”를 먼저 묻는 편이 좋습니다.

앞의 판단 흐름은 함수 오버로딩, 명시적 특수화, 부분 특수화를 구분하는 판단 흐름입니다. 이 세 선택은 한 프로그램에서 함께 사용할 수도 있습니다. SFINAE는 특정 템플릿의 대입 실패를 후보 제외로 처리하는 규칙이고, if constexpr는 선택된 본문의 컴파일 시간 분기입니다. 여기서는 용도를 구분하는 데까지만 다루며 앞선 초보 예제 실행에 두 도구를 구현할 필요는 없습니다.

명시적 특수화 선언은 그 특수화가 암시적으로 생성될 사용보다 앞서 도달 가능해야 합니다. 본문의 mySwap 특수화를 main 위에 둔 이유입니다. 명시적 특수화의 선언·사용 조건, 부분 특수화의 일치·모호성 규칙, 문자열 포인터 접근자를 필요할 때 함께 확인하세요.