Docs
README
Function Templates in C++
Table of Contents
- •What are Templates
- •How Template Instantiation Works
- •Basic Syntax
- •Type Deduction
- •Multiple Type Parameters
- •Non-Type Parameters
- •Template Specialization
- •Best Practices
What are Templates
Templates enable generic programming - write code once, use with any type. The compiler generates specific versions for each type you use.
The Problem Templates Solve
┌─────────────────────────────────────────────────────────────────────────────┐
│ WITHOUT TEMPLATES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ You need a max function for different types... │
│ │
│ int maxInt(int a, int b) { return a > b ? a : b; } │
│ double maxDouble(double a, double b) { return a > b ? a : b; } │
│ float maxFloat(float a, float b) { return a > b ? a : b; } │
│ long maxLong(long a, long b) { return a > b ? a : b; } │
│ string maxString(string a, string b) { return a > b ? a : b; } │
│ │
│ ❌ Repetitive code! │
│ ❌ Easy to introduce bugs │
│ ❌ Must add new function for each type │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ WITH TEMPLATES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ template<typename T> │
│ T max(T a, T b) { return a > b ? a : b; } │
│ │
│ max(1, 2); // Works with int │
│ max(1.5, 2.5); // Works with double │
│ max('a', 'z'); // Works with char │
│ max(str1, str2); // Works with string │
│ │
│ ✅ Write once, works with ANY type that supports > │
│ ✅ Type-safe (compiler catches mismatches) │
│ ✅ No runtime overhead (resolved at compile time) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
How Template Instantiation Works
When you use a template with a specific type, the compiler generates actual code for that type. This is called template instantiation.
Instantiation Process Visualization
┌─────────────────────────────────────────────────────────────────────────────┐
│ TEMPLATE INSTANTIATION PROCESS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ SOURCE CODE (what you write): │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ template<typename T> │ │
│ │ T square(T x) { return x * x; } │ │
│ │ │ │
│ │ int main() { │ │
│ │ square(5); // Use with int │ │
│ │ square(3.14); // Use with double │ │
│ │ square(2.5f); // Use with float │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ COMPILER SEES USAGES │
│ and generates code: │
│ │ │
│ ┌─────────────────────────┼─────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ T = int │ │ T = double │ │ T = float │ │
│ │ │ │ │ │ │ │
│ │ int square │ │double square │ │float square │ │
│ │ (int x) { │ │(double x) { │ │(float x) { │ │
│ │ return x*x;│ │ return x*x; │ │ return x*x; │ │
│ │ } │ │} │ │} │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ COMPILED BINARY contains ALL THREE versions │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Key Points About Instantiation
| Aspect | Description |
|---|---|
| When | At compile time, when template is used with a specific type |
| Where | Only for types actually used in your code |
| Code Bloat? | Each instantiation creates new code, but only for used types |
| Header Files | Templates must be defined in headers (not .cpp files) |
Basic Syntax
template<typename T> // or template<class T>
T functionName(T param) {
// T is the placeholder type
return param;
}
Examples
template<typename T>
T square(T x) {
return x * x;
}
template<typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<typename T>
void print(const vector<T>& vec) {
for (const T& item : vec) {
cout << item << " ";
}
}
Type Deduction
Compiler deduces T from arguments:
template<typename T>
T add(T a, T b) { return a + b; }
add(1, 2); // T deduced as int
add(1.5, 2.5); // T deduced as double
// Explicit specification
add<long>(1, 2); // Force T = long
Deduction Conflicts
template<typename T>
T add(T a, T b) { return a + b; }
// add(1, 2.5); // ERROR: T = int or double?
// Solutions:
add<double>(1, 2.5); // Explicit
add(1.0, 2.5); // Make types match
Multiple Type Parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// C++14 simplified
template<typename T, typename U>
auto add(T a, U b) {
return a + b;
}
add(1, 2.5); // int + double = double
add(1.5, 2); // double + int = double
Return Type Issues
template<typename T, typename U>
T badAdd(T a, U b) { return a + b; } // Truncates if U > T
template<typename T, typename U>
auto goodAdd(T a, U b) { return a + b; } // Correct return type
Non-Type Parameters
template<typename T, int Size>
class Array {
T data[Size];
public:
int size() const { return Size; }
};
Array<int, 10> arr; // Fixed size at compile time
// Function with non-type parameter
template<int N>
int multiply(int x) {
return x * N;
}
multiply<5>(10); // Returns 50
Template Specialization
Override template for specific types:
// Primary template
template<typename T>
T absolute(T x) {
return x < 0 ? -x : x;
}
// Specialization for bool
template<>
bool absolute<bool>(bool x) {
return x; // No negation for bool
}
// Partial specialization (for pointers)
template<typename T>
T* absolute(T* ptr) {
return ptr; // Just return pointer
}
Why Specialize?
// Generic print
template<typename T>
void print(const T& value) {
cout << value;
}
// Specialize for C-strings
template<>
void print<const char*>(const char* const& value) {
cout << "\"" << value << "\"";
}
// Specialize for bool
template<>
void print<bool>(const bool& value) {
cout << (value ? "true" : "false");
}
Best Practices
✅ Do
// 1. Use auto for return types (C++14)
template<typename T, typename U>
auto add(T a, U b) { return a + b; }
// 2. Use const& for non-modified parameters
template<typename T>
void process(const T& item);
// 3. Use forwarding references for perfect forwarding
template<typename T>
void forward(T&& arg);
// 4. Define in headers
// Templates must be visible at instantiation
❌ Don't
// 1. Don't assume operators exist
template<typename T>
T multiply(T a, T b) { return a * b; } // T must support *
// 2. Don't separate declaration/definition
// template.h - declaration only - WON'T WORK
// template.cpp - definition - WON'T LINK
Quick Reference
// Basic template
template<typename T>
T func(T param);
// Multiple types
template<typename T, typename U>
auto func(T a, U b);
// Non-type parameter
template<typename T, int N>
void func(T (&arr)[N]);
// Specialization
template<>
void func<int>(int param);
// Default template argument
template<typename T = int>
T func();
Compile & Run
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples