Skip to content

🎯 Template Specialization — Handle Edge Cases

Specialization cho phép customize behavior cho specific types — xử lý edge cases mà generic template không cover.

Tại sao cần Specialization?

cpp
template<typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << maximum(10, 20) << std::endl;  // ✅ 20
    std::cout << maximum(3.14, 2.71) << std::endl;  // ✅ 3.14
    
    // ❌ C-strings compare POINTERS, not content!
    const char* s1 = "apple";
    const char* s2 = "banana";
    std::cout << maximum(s1, s2) << std::endl;  // Compares addresses!
    
    return 0;
}

Giải pháp: Specialize cho const char*


Full Specialization (Function)

cpp
#include <iostream>
#include <cstring>

// Primary template
template<typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Full specialization for const char*
template<>
const char* maximum<const char*>(const char* a, const char* b) {
    return (std::strcmp(a, b) > 0) ? a : b;
}

int main() {
    std::cout << maximum(10, 20) << std::endl;  // 20
    
    const char* s1 = "apple";
    const char* s2 = "banana";
    std::cout << maximum(s1, s2) << std::endl;  // banana ✅
    
    return 0;
}

Full Specialization (Class)

cpp
#include <iostream>
#include <cstring>

// Primary template
template<typename T>
class Printer {
public:
    static void print(const T& value) {
        std::cout << "Generic: " << value << std::endl;
    }
};

// Full specialization for bool
template<>
class Printer<bool> {
public:
    static void print(bool value) {
        std::cout << "Bool: " << (value ? "true" : "false") << std::endl;
    }
};

// Full specialization for const char*
template<>
class Printer<const char*> {
public:
    static void print(const char* value) {
        std::cout << "C-String: \"" << value << "\"" << std::endl;
    }
};

int main() {
    Printer<int>::print(42);           // Generic: 42
    Printer<double>::print(3.14);      // Generic: 3.14
    Printer<bool>::print(true);        // Bool: true
    Printer<const char*>::print("hi"); // C-String: "hi"
    
    return 0;
}

Partial Specialization

Partial specialization chỉ áp dụng cho class templates (không cho function templates).

Example: Pointer Specialization

cpp
#include <iostream>

// Primary template
template<typename T>
class TypeInfo {
public:
    static void describe() {
        std::cout << "Regular type" << std::endl;
    }
};

// Partial specialization for pointers
template<typename T>
class TypeInfo<T*> {
public:
    static void describe() {
        std::cout << "Pointer to ";
        TypeInfo<T>::describe();
    }
};

// Partial specialization for references
template<typename T>
class TypeInfo<T&> {
public:
    static void describe() {
        std::cout << "Reference to ";
        TypeInfo<T>::describe();
    }
};

int main() {
    TypeInfo<int>::describe();      // Regular type
    TypeInfo<int*>::describe();     // Pointer to Regular type
    TypeInfo<int**>::describe();    // Pointer to Pointer to Regular type
    TypeInfo<int&>::describe();     // Reference to Regular type
    
    return 0;
}

Example: Array Specialization

cpp
template<typename T>
class Container {
    T data_;
public:
    Container(const T& d) : data_(d) {}
    void print() { std::cout << "Single: " << data_ << std::endl; }
};

// Partial specialization for arrays
template<typename T, std::size_t N>
class Container<T[N]> {
    T data_[N];
public:
    Container(const T (&arr)[N]) {
        std::copy(std::begin(arr), std::end(arr), data_);
    }
    void print() {
        std::cout << "Array of " << N << ": ";
        for (const auto& elem : data_) {
            std::cout << elem << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    Container<int> single(42);
    single.print();  // Single: 42
    
    int arr[] = {1, 2, 3, 4, 5};
    Container<int[5]> array(arr);
    array.print();  // Array of 5: 1 2 3 4 5
    
    return 0;
}

Partial Specialization Patterns

One of Multiple Parameters

cpp
template<typename T, typename U>
class Pair {
public:
    static void describe() {
        std::cout << "Generic Pair" << std::endl;
    }
};

// Specialize when both types are the same
template<typename T>
class Pair<T, T> {
public:
    static void describe() {
        std::cout << "Same-type Pair" << std::endl;
    }
};

// Specialize when second is int
template<typename T>
class Pair<T, int> {
public:
    static void describe() {
        std::cout << "Pair with int second" << std::endl;
    }
};

int main() {
    Pair<double, std::string>::describe();  // Generic Pair
    Pair<int, int>::describe();             // Same-type Pair
    Pair<std::string, int>::describe();     // Pair with int second
    
    return 0;
}

Type Traits Implementation

cpp
#include <iostream>

// Primary: not a pointer
template<typename T>
struct is_pointer {
    static constexpr bool value = false;
};

// Partial specialization: IS a pointer
template<typename T>
struct is_pointer<T*> {
    static constexpr bool value = true;
};

// Helper variable template (C++14)
template<typename T>
constexpr bool is_pointer_v = is_pointer<T>::value;

int main() {
    std::cout << std::boolalpha;
    std::cout << is_pointer_v<int> << std::endl;    // false
    std::cout << is_pointer_v<int*> << std::endl;   // true
    std::cout << is_pointer_v<int**> << std::endl;  // true
    
    return 0;
}

More Type Traits

cpp
// Remove const
template<typename T>
struct remove_const {
    using type = T;
};

template<typename T>
struct remove_const<const T> {
    using type = T;
};

template<typename T>
using remove_const_t = typename remove_const<T>::type;

// Remove reference
template<typename T>
struct remove_reference {
    using type = T;
};

template<typename T>
struct remove_reference<T&> {
    using type = T;
};

template<typename T>
struct remove_reference<T&&> {
    using type = T;
};

template<typename T>
using remove_reference_t = typename remove_reference<T>::type;

Priority Order

Compiler chọn theo thứ tự ưu tiên:

┌─────────────────────────────────────────────────────────────────┐
│                 SPECIALIZATION PRIORITY                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Non-template function (exact match)                         │
│  2. Full specialization                                         │
│  3. Partial specialization (more specific wins)                │
│  4. Primary template                                            │
│                                                                 │
│  Example:                                                       │
│  Template\<T, U\>           ← Primary                            │
│  Template\<T, T\>           ← Partial (same types)               │
│  Template\<T, int\>         ← Partial (int second)               │
│  Template\<int, int\>       ← Full specialization                │
│                                                                 │
│  Template\<int, int\> wins over Template\<T, T\>!                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Real-world Example: std::vector<bool>

cpp
// This is why std::vector<bool> is "special" (and controversial)

// Primary template (simplified)
template<typename T>
class vector {
    T* data_;
    // Normal storage
};

// Specialization for bool — packed bits
template<>
class vector<bool> {
    unsigned char* data_;
    // 8 bools per byte — space efficient but different behavior!
};

// ⚠️ Đây là lý do vector<bool> controversial:
// - operator[] không trả về bool&, mà proxy object
// - Không thể lấy pointer tới element

📚 Tổng kết

TypeSyntaxUse Case
Full (function)template<> T func<Type>(...)Specific type handling
Full (class)template<> class Foo<Type>Complete rewrite
Partialtemplate<typename T> class Foo<T*>Pattern matching
Type traitsRemove const/ref/pointerMetaprogramming

⚠️ Avoid Over-specialization

Quá nhiều specializations làm code khó maintain. Ưu tiên dùng concepts (C++20) thay vì SFINAE + specialization.


➡️ Tiếp theo

Tiếp theo: C++20 Concepts — Modern template constraints.

🧠 Quiz

Câu 1: Partial specialization khác Full specialization như thế nào?

  • [x] A) Partial specialize cho pattern (vd: tất cả pointer types), Full specialize cho một type cụ thể
  • [ ] B) Partial chỉ dùng cho functions, Full chỉ dùng cho classes
  • [ ] C) Partial nhanh hơn Full vì ít code
  • [ ] D) Không có sự khác biệt, chỉ là tên gọi khác

💡 Giải thích: Full specialization (template<>) cung cấp implementation cho một type cụ thể (vd: Printer<bool>). Partial specialization vẫn giữ template parameter nhưng thu hẹp pattern (vd: TypeInfo<T*> cho mọi pointer type). Lưu ý: partial specialization chỉ áp dụng cho class templates, không cho function templates.

Câu 2: Compiler chọn specialization nào cho Pair<int, int>?

cpp
template<typename T, typename U> class Pair { };         // (1) Primary
template<typename T> class Pair<T, T> { };               // (2) Same-type
template<typename T> class Pair<T, int> { };             // (3) Second is int
template<> class Pair<int, int> { };                     // (4) Full spec
  • [ ] A) (1) Primary template
  • [ ] B) (2) Same-type partial specialization
  • [ ] C) (3) Second-is-int partial specialization
  • [x] D) (4) Full specialization — luôn được ưu tiên cao nhất

💡 Giải thích: Thứ tự ưu tiên của compiler: Full specialization > Partial specialization (cụ thể hơn thắng) > Primary template. Pair<int, int> match cả (2), (3) và (4), nhưng (4) là full specialization nên được ưu tiên. Nếu không có (4), compiler phải chọn giữa (2) và (3) — sẽ gây ambiguity error.

Câu 3: Tại sao std::vector<bool> bị coi là controversial?

  • [ ] A) Vì nó không compile được trên một số compilers
  • [ ] B) Vì nó chậm hơn vector<int>
  • [x] C) Vì full specialization thay đổi behavior — operator[] trả về proxy object thay vì bool&
  • [ ] D) Vì nó không thể chứa hơn 64 phần tử

💡 Giải thích: std::vector<bool> là full specialization lưu bools dưới dạng packed bits (8 bools/byte). Vì không thể tạo reference tới individual bit, operator[] trả về proxy object thay vì bool&. Điều này phá vỡ interface chuẩn của vector — &v[0] không hoạt động, và nhiều generic code fails. Đây là bài học về nguy cơ khi specialization thay đổi semantic quá nhiều.