Templates: Compile-Time Polymorphism
Templates enable writing code that works with any type satisfying certain requirements, with full type checking at compile time and zero runtime overhead. Unlike runtime polymorphism (virtual functions), templates generate specialized code for each type used.
Function Templates
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
auto x = max(10, 20); // T = int
auto y = max(3.14, 2.71); // T = double
auto z = max<long>(5, 10); // Explicit T = long
Template Argument Deduction
The compiler deduces template parameters from function arguments. Deduction follows strict rules:
template<typename T>
void process(T value, T* ptr);
int x = 5;
process(x, &x); // OK: T = int
double d = 3.14;
// process(x, &d); // Error: T is int or double?
Class Templates
template<typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& value) { data_.push_back(value); }
T pop() {
T value = data_.back();
data_.pop_back();
return value;
}
bool empty() const { return data_.empty(); }
};
Stack<int> intStack;
Stack<std::string> stringStack;
Member Function Templates
Templates can appear at multiple levels:
template<typename T>
class Container {
public:
// Member function template
template<typename U>
void insert(U&& value) {
// ...
}
};
Container<int> c;
c.insert(42); // U = int
c.insert("hello"); // U = const char*
Template Specialization
Full Specialization
Provide a completely different implementation for a specific type:
template<typename T>
class TypeName {
public:
static const char* get() { return "unknown"; }
};
template<>
class TypeName<int> {
public:
static const char* get() { return "int"; }
};
template<>
class TypeName<double> {
public:
static const char* get() { return "double"; }
};
Partial Specialization (Class Templates Only)
Specialize for a subset of template parameters:
template<typename T, typename U>
class Pair {
T first_;
U second_;
};
// Partial specialization: both types the same
template<typename T>
class Pair<T, T> {
T first_, second_;
public:
bool equal() const { return first_ == second_; }
};
// Partial specialization: pointer types
template<typename T>
class Pair<T*, T*> {
// Special implementation for pointers
};
Non-Type Template Parameters
Templates can take compile-time constant values:
template<typename T, size_t N>
class Array {
T data_[N];
public:
constexpr size_t size() const { return N; }
T& operator[](size_t i) { return data_[i]; }
const T& operator[](size_t i) const { return data_[i]; }
};
Array<int, 10> arr1; // Different type from...
Array<int, 20> arr2; // ...this
Non-type parameters can be: integers, enums, pointers, references, nullptr_t, and (C++20) floating-point and class types.
Variadic Templates (C++11)
Templates accepting arbitrary numbers of arguments:
template<typename... Args>
void print(Args... args) {
((std::cout << args << ' '), ...); // Fold expression (C++17)
std::cout << '\\n';
}
print(1, 2.5, "hello", 'x'); // Any number/types of arguments
Parameter Pack Expansion
template<typename... Ts>
class Tuple; // Declaration
// Recursive inheritance
template<typename T, typename... Ts>
class Tuple<T, Ts...> : public Tuple<Ts...> {
T value_;
public:
Tuple(T v, Ts... vs) : Tuple<Ts...>(vs...), value_(v) {}
};
template<>
class Tuple<> { // Base case
};
SFINAE: Substitution Failure Is Not An Error
When template substitution fails, the candidate is removed from overload set instead of causing error:
template<typename T>
typename T::value_type getValue(T container) { // Only if T has value_type
return container[0];
}
template<typename T>
T getValue(T value) { // Fallback
return value;
}
std::vector<int> vec{1, 2, 3};
auto x = getValue(vec); // Calls first overload
auto y = getValue(42); // Calls second overload (first SFINAE'd out)
Modern C++ uses std::enable_if for SFINAE:
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
increment(T value) {
return value + 1;
}
increment(10); // OK
// increment(3.14); // Error: no matching function
C++20 concepts provide cleaner syntax:
template<std::integral T>
T increment(T value) {
return value + 1;
}
What happens when you instantiate Array<int, 5> and Array<int, 10>?
Interactive Lab
Waiting for signal...