C++ templates enable code reuse by allowing type parametrization—defining logic that operates on unspecified types, which the compiler resolves into concrete implementations based on user-provided type arguments. This eliminates rigid type constraints in statically-typed C++ code.
Compilers do not compile template definitions directly. Instead, they generate type-specific code (instantiation) when a template is used with explicit or deduced type parameters. Different type arguments produce distinct compiled implementations.
Class Templates
Class templates describe families of type-safe, logically related classes distinguished solely by type or value parameters (e.g., integers, pointers/references to global variables). They are ideal for generic data structures.
Syntax and Usage:
#include <iostream>
#include <cmath>
using namespace std;
template <typename U>
class TypeComparer {
public:
bool isIdentical(U x, U y);
};
template <typename U>
bool TypeComparer<U>::isIdentical(U x, U y) {
return x == y;
}
// Full specialization for float type
template<>
class TypeComparer<float> {
public:
bool isIdentical(float x, float y);
};
bool TypeComparer<float>::isIdentical(float x, float y) {
return abs(x - y) < 1e-3;
}
int main() {
TypeComparer<int> intComparer;
cout << intComparer.isIdentical(1, 2) << endl;
TypeComparer<float> floatComparer;
cout << floatComparer.isIdentical(1.001f, 1.001f) << endl;
return 0;
}
Partial Specialization vs Full Specialization
- Full specialization fixes all type parameters of a class template.
- Partial specialization fixes only a subset of type parameters, leaving the rest as variables.
Example:
#include <iostream>
using namespace std;
template <typename V, typename W>
class PairWrapper {
public:
PairWrapper(V val1, W val2) : a(val1), b(val2) {
cout << "Generic template instantiated" << endl;
}
private:
V a;
W b;
};
// Full specialization for <int, int>
template<>
class PairWrapper<int, int> {
public:
PairWrapper(int val1, int val2) : a(val1), b(val2) {
cout << "Full specialization for <int, int>" << endl;
}
private:
int a;
int b;
};
// Partial specialization: first type is fixed to int, second remains variable
template <typename W>
class PairWrapper<int, W> {
public:
PairWrapper(int val1, W val2) : a(val1), b(val2) {
cout << "Partial specialization: first type = int" << endl;
}
private:
int a;
W b;
};
int main() {
PairWrapper<double, double> pw1(1.01, 1.01);
PairWrapper<int, int> pw2(1, 1);
PairWrapper<int, const char*> pw3(1, "hello");
return 0;
}
Function Templates
Function templates define generic functions. Unlike class templates, they support only full specialization; partial specialization is prohibited in standard C++. This limitation is intentional—functon overloading achieves the same flexibility as partial specialization, while avoiding ambiguity in overload resolution.
Function templates and they specializations interact with overload resolution as follows:
- Non-template functions take highest priority if parameter types match exactly.
- If no matching non-template function exists, the compiler selects the most specialized applicable base template.
- If a selected base template has a full specialization for the deduced type, the specialization is used.
Example of Function Template Overload and Specialization:
#include <iostream>
using namespace std;
template <typename T>
void process(T arg) {
cout << "Generic function: " << arg << endl;
}
template <typename T>
void process(T* arg) {
cout << "Overloaded pointer version: " << *arg << endl;
}
template<>
void process<int>(int arg) {
cout << "Full specialization for int: " << arg << endl;
}
void process(double arg) {
cout << "Non-template double version: " << arg << endl;
}
int main() {
bool flag = true;
int num = 42;
double dbl = 3.14;
process(flag); // Calls generic process<bool>
process(&num); // Calls pointer overload
process(num); // Calls full specialization process<int>
process(dbl); // Calls non-template double function
return 0;
}