Skip to content

Template_specialization

Template specialization allows you to provide custom implementations of templates for specific types, enabling type-specific optimizations and behavior.

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

Complete override of the template for a specific type.

#include <iostream>
#include <vector>
// Generic template
template<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 bool
template<>
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;
}
#include <iostream>
#include <cstring>
// Generic template
template<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 specialize
template<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;
}

Partially specialize class templates, fixing some parameters while leaving others generic.

#include <iostream>
#include <memory>
// Generic template
template<typename T>
class PointerHolder {
T* ptr_;
public:
explicit PointerHolder(T* p) : ptr_(p) {}
T* get() const { return ptr_; }
};
// Partial: specialize for all pointer types
template<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;
}
#include <iostream>
// Generic: two different types
template<typename T, typename U>
struct Pair {
T first;
U second;
Pair(T f, U s) : first(f), second(s) {}
};
// Partial: same type for both
template<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, double
template<>
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;
}
#include <iostream>
#include <memory>
// Generic deleter storage
template<typename T, typename Deleter = std::default_delete<T>>
class UniquePtr {};
// Partial specialization for arrays
template<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;
}
#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;
}
  1. Class templates: Full and partial specialization are both useful
  2. Function templates: Prefer overloading to specialization
  3. Type traits: Use specialization extensively for metaprogramming
#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;
}
#include <iostream>
#include <type_traits>
// Primary template
template<typename T, typename = void>
struct IsPointer : std::false_type {};
// Specialization for pointer types
template<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;
}
#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 hash
template<>
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 - trivial
template<>
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;
}
#include <iostream>
#include <sstream>
// Generic serializer
template<typename T>
class Serializer {
public:
static std::string serialize(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
};
// Specialization for bool
template<>
class Serializer<bool> {
public:
static std::string serialize(bool value) {
return value ? "true" : "false";
}
};
// Specialization for pointers
template<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;
}
  1. Functions: Prefer overloading to specialization
  2. Classes: Use full and partial specialization as needed
  3. Avoid over-specialization: Keep specialization count low
  4. Consider concepts (C++20): For constraints, concepts are often cleaner
  5. Document specializations: Clearly document why each specialization exists
  • 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