Template_specialization
Template Specialization
Section titled “Template Specialization”Template specialization allows you to provide custom implementations of templates for specific types, enabling type-specific optimizations and behavior.
Introduction
Section titled “Introduction”When the generic template doesn’t suit a particular type, specialization lets you override it. This is useful for:
- Type-specific optimizations
- Different behavior for certain types
- Handling types that don’t support all operations
Full (Explicit) Specialization
Section titled “Full (Explicit) Specialization”Complete override of the template for a specific type.
Class Template Specialization
Section titled “Class Template Specialization”#include <iostream>#include <vector>
// Generic templatetemplate<typename T>class Storage {private: T data_;
public: explicit Storage(const T& data) : data_(data) {} void print() const { std::cout << "Generic: " << data_ << "\n"; }};
// Full specialization for booltemplate<>class Storage<bool> {private: bool data_;
public: explicit Storage(bool data) : data_(data) {} void print() const { std::cout << "Bool: " << (data_ ? "true" : "false") << "\n"; }};
// Full specialization for std::vector<int>template<>class Storage<std::vector<int>> {private: std::vector<int> data_;
public: explicit Storage(const std::vector<int>& data) : data_(data) {} void print() const { std::cout << "Vector<int>: "; for (int x : data_) std::cout << x << " "; std::cout << "\n"; }};
int main() { Storage<int> s1(42); s1.print(); // Generic: 42
Storage<bool> s2(true); s2.print(); // Bool: true
Storage<std::vector<int>> s3({1, 2, 3}); s3.print(); // Vector<int>: 1 2 3
return 0;}Function Template Specialization
Section titled “Function Template Specialization”#include <iostream>#include <cstring>
// Generic templatetemplate<typename T>void printValue(const T& value) { std::cout << "Generic: " << value << "\n";}
// Specialization for const char*template<>void printValue(const char* const& value) { std::cout << "C-string: \"" << value << "\"\n";}
// Better approach: overload instead of specializetemplate<typename T>void printAnything(const T& value) { std::cout << value << "\n";}
void printAnything(const char* value) { std::cout << "Overload: \"" << value << "\"\n";}
int main() { printValue(42); printValue("Hello"); // Uses specialization
printAnything("World"); // Uses overload return 0;}Partial (Partial) Specialization
Section titled “Partial (Partial) Specialization”Partially specialize class templates, fixing some parameters while leaving others generic.
Pointer Specialization
Section titled “Pointer Specialization”#include <iostream>#include <memory>
// Generic templatetemplate<typename T>class PointerHolder { T* ptr_;public: explicit PointerHolder(T* p) : ptr_(p) {} T* get() const { return ptr_; }};
// Partial: specialize for all pointer typestemplate<typename T>class PointerHolder<T*> { T* ptr_;public: explicit PointerHolder(T* p) : ptr_(p) {} T* get() const { return ptr_; } T& operator*() const { return *ptr_; }};
int main() { int x = 42; PointerHolder<int> h1(&x); std::cout << *h1 << "\n"; // 42
PointerHolder<int*> h2(&x); std::cout << **h2 << "\n"; // 42
return 0;}Multiple Template Parameters
Section titled “Multiple Template Parameters”#include <iostream>
// Generic: two different typestemplate<typename T, typename U>struct Pair { T first; U second; Pair(T f, U s) : first(f), second(s) {}};
// Partial: same type for bothtemplate<typename T>struct Pair<T, T> { T first; T second; Pair(T f, T s) : first(f), second(s) {}
// Additional method for same-type pairs T sum() const { return first + second; }};
// Full specialization for int, doubletemplate<>struct Pair<int, double> { int first; double second; Pair(int f, double s) : first(f), second(s) {} double weightedSum() const { return first * 0.3 + second * 0.7; }};
int main() { Pair<int, std::string> p1(1, "hello"); Pair<int, int> p2(10, 20); // Uses same-type specialization std::cout << p2.sum() << "\n"; // 30
Pair<int, double> p3(5, 3.14); std::cout << p3.weightedSum() << "\n"; // Uses full specialization
return 0;}std::unique_ptr Specialization Example
Section titled “std::unique_ptr Specialization Example”#include <iostream>#include <memory>
// Generic deleter storagetemplate<typename T, typename Deleter = std::default_delete<T>>class UniquePtr {};
// Partial specialization for arraystemplate<typename T, typename Deleter>class UniquePtr<T[], Deleter> { T* ptr_; Deleter deleter_;public: explicit UniquePtr(T* p) : ptr_(p) {} ~UniquePtr() { deleter_(ptr_); }
T& operator[](size_t i) { return ptr_[i]; } const T& operator[](size_t i) const { return ptr_[i]; }
// Disable regular pointer operations for arrays T* operator->() = delete; T& operator*() = delete;};
int main() { // Use regular unique_ptr for single objects auto single = std::make_unique<int>(42);
// Use array specialization int* arr = new int[3]{1, 2, 3}; UniquePtr<int[]> arrayPtr(arr); std::cout << arrayPtr[0] << " " << arrayPtr[1] << "\n";
return 0;}Specialization vs Overloading
Section titled “Specialization vs Overloading”Function Templates: Prefer Overloading
Section titled “Function Templates: Prefer Overloading”#include <iostream>
// Overload (preferred for functions)template<typename T>void process(T value) { std::cout << "Template: " << value << "\n";}
void process(const char* value) { std::cout << "Overload: \"" << value << "\"\n";}
// Specialization (not recommended for functions)template<>void process<int>(int value) { std::cout << "Specialized int: " << value << "\n";}
int main() { process(42); // Calls specialization process("hello"); // Calls overload process(3.14); // Calls template
return 0;}When to Use Specialization
Section titled “When to Use Specialization”- Class templates: Full and partial specialization are both useful
- Function templates: Prefer overloading to specialization
- Type traits: Use specialization extensively for metaprogramming
Member Function Specialization
Section titled “Member Function Specialization”#include <iostream>
template<typename T>class Container { T data_;public: Container(T d) : data_(d) {}
// Generic member void print() const { std::cout << "Container: " << data_ << "\n"; }
// Specialized member for pointers template<typename U> void print() const requires std::is_pointer_v<T> { std::cout << "Pointer: " << *data_ << "\n"; }};
int main() { Container<int> c1(42); c1.print(); // Container: 42
int x = 100; Container<int*> c2(&x); c2.print(); // Pointer: 100
return 0;}SFINAE with Specialization
Section titled “SFINAE with Specialization”#include <iostream>#include <type_traits>
// Primary templatetemplate<typename T, typename = void>struct IsPointer : std::false_type {};
// Specialization for pointer typestemplate<typename T>struct IsPointer<T, std::void_t<std::remove_pointer_t<T>*>> : std::true_type {};
template<typename T>inline constexpr bool IsPointer_v = IsPointer<T>::value;
int main() { std::cout << IsPointer_v<int> << "\n"; // 0 std::cout << IsPointer_v<int*> << "\n"; // 1 std::cout << IsPointer_v<int**> << "\n"; // 1
return 0;}Practical Examples
Section titled “Practical Examples”Type-Specific Hash Function
Section titled “Type-Specific Hash Function”#include <iostream>#include <string>#include <functional>
template<typename T>struct Hasher { size_t operator()(const T& value) const { return std::hash<T>{}(value); }};
// Specialization for strings - custom hashtemplate<>struct Hasher<std::string> { size_t operator()(const std::string& value) const { // FNV-1a hash size_t hash = 2166136261u; for (char c : value) { hash ^= static_cast<size_t>(c); hash *= 16777619u; } return hash; }};
// Specialization for bool - trivialtemplate<>struct Hasher<bool> { size_t operator()(bool value) const { return value ? 1 : 0; }};
int main() { Hasher<int> intHasher; Hasher<std::string> strHasher; Hasher<bool> boolHasher;
std::cout << intHasher(42) << "\n"; std::cout << strHasher("hello") << "\n"; std::cout << boolHasher(true) << "\n";
return 0;}Serialization
Section titled “Serialization”#include <iostream>#include <sstream>
// Generic serializertemplate<typename T>class Serializer {public: static std::string serialize(const T& value) { std::ostringstream oss; oss << value; return oss.str(); }};
// Specialization for booltemplate<>class Serializer<bool> {public: static std::string serialize(bool value) { return value ? "true" : "false"; }};
// Specialization for pointerstemplate<typename T>class Serializer<T*> {public: static std::string serialize(T* value) { if (value) { return "ptr:" + Serializer<T>::serialize(*value); } return "null"; }};
int main() { std::cout << Serializer<int>::serialize(42) << "\n"; std::cout << Serializer<bool>::serialize(true) << "\n";
int x = 100; std::cout << Serializer<int*>::serialize(&x) << "\n";
return 0;}Best Practices
Section titled “Best Practices”- Functions: Prefer overloading to specialization
- Classes: Use full and partial specialization as needed
- Avoid over-specialization: Keep specialization count low
- Consider concepts (C++20): For constraints, concepts are often cleaner
- Document specializations: Clearly document why each specialization exists
Key Takeaways
Section titled “Key Takeaways”- Full specialization: Override completely for a specific type
- Partial specialization: Override for a category of types
- Prefer function overloading to function specialization
- Specialization affects which template is chosen at compile time
- Use sparingly - can make code harder to maintain