Imagine a cookie cutter - you can use the same shape to make cookies from different doughs: chocolate, vanilla, or gingerbread. Templates are like cookie cutters for code - one pattern that works with many types!
Function templates let you write one function that works with any type - like a universal adapter!
// Simple function template
template
T max(T a, T b) {
return (a > b) ? a : b;
}
// Multiple type parameters
template
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// Template with arrays
template
T sum(T (&arr)[SIZE]) {
T total = 0;
for (int i = 0; i < SIZE; i++) {
total += arr[i];
}
return total;
}
// Using the templates
int main() {
// Type deduction
cout << max(10, 20) << endl; // T = int
cout << max(3.14, 2.71) << endl; // T = double
cout << max('a', 'z') << endl; // T = char
// Explicit type specification
cout << max(10, 3.14) << endl; // Forces double
// Mixed types
cout << add(5, 3.14) << endl; // int + double
// Array template
int numbers[] = {1, 2, 3, 4, 5};
cout << "Sum: " << sum(numbers) << endl; // SIZE deduced as 5
return 0;
}
Sometimes you need a special recipe for a specific ingredient. Template specialization lets you provide custom implementations for specific types!
// Generic template
template
class Storage {
private:
T value;
public:
Storage(T val) : value(val) {}
void print() {
cout << "Generic storage: " << value << endl;
}
T getValue() { return value; }
};
// Full specialization for bool
template<>
class Storage {
private:
bool value;
public:
Storage(bool val) : value(val) {}
void print() {
cout << "Bool storage: " << (value ? "TRUE" : "FALSE") << endl;
}
bool getValue() { return value; }
};
// Partial specialization for pointers
template
class Storage {
private:
T* ptr;
public:
Storage(T* p) : ptr(p) {}
void print() {
if (ptr) {
cout << "Pointer storage: *ptr = " << *ptr << endl;
} else {
cout << "Pointer storage: NULL" << endl;
}
}
T* getValue() { return ptr; }
~Storage() {
// Note: Doesn't delete - just stores pointer
}
};
Class templates are like blueprints for blueprints - they let you create classes that work with any type!
template
class DynamicArray {
private:
T* data;
int size;
int capacity;
void resize() {
capacity *= 2;
T* newData = new T[capacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}
public:
// Constructor
DynamicArray(int initialCapacity = 10)
: size(0), capacity(initialCapacity) {
data = new T[capacity];
}
// Destructor
~DynamicArray() {
delete[] data;
}
// Copy constructor
DynamicArray(const DynamicArray& other)
: size(other.size), capacity(other.capacity) {
data = new T[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
// Add element
void push_back(const T& value) {
if (size == capacity) {
resize();
}
data[size++] = value;
}
// Access elements
T& operator[](int index) {
if (index < 0 || index >= size) {
throw out_of_range("Index out of bounds");
}
return data[index];
}
// Get size
int getSize() const { return size; }
// Iterator support
T* begin() { return data; }
T* end() { return data + size; }
};
// Usage
int main() {
// Array of integers
DynamicArray numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
// Array of strings
DynamicArray words;
words.push_back("Hello");
words.push_back("Template");
words.push_back("World");
// Using range-based for loop
for (int num : numbers) {
cout << num << " ";
}
return 0;
}
Templates can take more than just types - they can take values too! It's like a recipe that needs both ingredients AND quantities.
// Fixed-size array template
template
class FixedArray {
private:
T data[SIZE]; // Stack-allocated array
public:
FixedArray() {
// Initialize all elements
for (int i = 0; i < SIZE; i++) {
data[i] = T();
}
}
T& operator[](int index) {
if (index < 0 || index >= SIZE) {
throw out_of_range("Index out of bounds");
}
return data[index];
}
constexpr int size() const { return SIZE; }
void fill(const T& value) {
for (int i = 0; i < SIZE; i++) {
data[i] = value;
}
}
};
// Matrix template with dimensions
template
class Matrix {
private:
T data[ROWS][COLS];
public:
Matrix() {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
data[i][j] = T();
}
}
}
T& at(int row, int col) {
return data[row][col];
}
Matrix transpose() {
Matrix result;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
result.at(j, i) = data[i][j];
}
}
return result;
}
};
// Usage
FixedArray smallArray;
FixedArray largeArray;
Matrix rotation;
Matrix data;
Variadic templates are like a recipe that can handle any number of ingredients - from a simple sandwich to a full feast!
// Print function for any number of arguments
template
void print(T&& t) {
cout << t << endl;
}
template
void print(T&& t, Args&&... args) {
cout << t << " ";
print(args...); // Recursive call with remaining args
}
// Type-safe printf
template
void safePrintf(const string& format, Args... args) {
printf(format.c_str(), args...);
}
// Sum any number of values
template
T sum(T t) {
return t;
}
template
T sum(T first, Args... rest) {
return first + sum(rest...);
}
// Create tuple-like structure
template
class Tuple {
// Implementation details...
};
// Factory function
template
unique_ptr make_unique(Args&&... args) {
return unique_ptr(new T(forward(args)...));
}
// Usage
print(1, 2.5, "Hello", 'A'); // Works with any types
cout << sum(1, 2, 3, 4, 5) << endl; // 15
auto ptr = make_unique("Hello, World!");
Template metaprogramming is like having a chef (compiler) prepare ingredients (compute values) before the restaurant (program) even opens!
// Compile-time factorial
template
struct Factorial {
static constexpr int value = N * Factorial::value;
};
// Base case specialization
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// Compile-time Fibonacci
template
struct Fibonacci {
static constexpr int value =
Fibonacci::value + Fibonacci::value;
};
template<>
struct Fibonacci<0> {
static constexpr int value = 0;
};
template<>
struct Fibonacci<1> {
static constexpr int value = 1;
};
// Type traits
template
struct is_pointer {
static constexpr bool value = false;
};
template
struct is_pointer {
static constexpr bool value = true;
};
// Conditional type selection
template
struct conditional {
using type = TrueType;
};
template
struct conditional {
using type = FalseType;
};
// Usage
constexpr int fact5 = Factorial<5>::value; // 120 at compile time
constexpr int fib10 = Fibonacci<10>::value; // 55 at compile time
cout << "5! = " << fact5 << endl;
cout << "Is int* a pointer? " << is_pointer::value << endl;
// Choose type based on condition
using MyType = conditional::type;
SFINAE is like a restaurant menu - if they don't have what you ordered, they don't shut down, they just say "we don't serve that"!
Create a template-based stack with the following features:
template
class Stack {
private:
// TODO: Add member variables
public:
Stack();
~Stack();
void push(const T& value);
void pop();
T& top();
const T& top() const;
bool empty() const;
size_t size() const;
// TODO: Add iterator support
class iterator {
// Iterator implementation
};
iterator begin();
iterator end();
};
// TODO: Specialize for bool
template<>
class Stack {
// Bit-packed implementation
};
// C++11: Auto return type
template
auto multiply(T t, U u) -> decltype(t * u) {
return t * u;
}
// C++14: Generic lambdas
auto genericLambda = [](auto x, auto y) {
return x + y;
};
// C++17: Class template argument deduction
pair p(1, 2.5); // Deduces pair
vector v{1, 2, 3, 4}; // Deduces vector
// C++17: Fold expressions
template
auto sum(Args... args) {
return (args + ...); // Fold expression
}
// C++20: Concepts
template
concept Numeric = requires(T a, T b) {
{ a + b } -> convertible_to;
{ a - b } -> convertible_to;
{ a * b } -> convertible_to;
{ a / b } -> convertible_to;
};
template
T calculate(T a, T b) {
return (a + b) * (a - b);
}
// Constexpr if (C++17)
template
string toString(T value) {
if constexpr (is_same_v) {
return value;
} else if constexpr (is_arithmetic_v) {
return to_string(value);
} else {
return "Unknown type";
}
}
Build a simple expression template system for vector operations:
template
class VecExpression {
public:
double operator[](size_t i) const {
return static_cast(*this)[i];
}
size_t size() const {
return static_cast(*this).size();
}
};
class Vec : public VecExpression {
// Vector implementation
};
template
class VecSum : public VecExpression> {
// Sum expression
};