What Is It?
What Are Templates in C++?
Templates are a compile-time mechanism in C++ that allow you to write generic code that works with any data type. Instead of writing separate functions or classes for int, double, string, etc., you write a single template, and the compiler generates the specific versions (called template instantiations) as needed.
C++ supports two kinds of templates: function templates and class templates.
// Function template: works with any type T
template<typename T>
T maxOf(T a, T b) {
return (a > b) ? a : b;
}
// Class template: works with any type T
template<typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
};
// Usage:
int m1 = maxOf(10, 20); // T = int
double m2 = maxOf(3.14, 2.71); // T = double
Box<string> b("Arjun"); // T = string
cout << b.get() << endl; // ArjunTemplates are the foundation of the entire C++ Standard Template Library (STL). Containers like vector<T>, map<K,V>, stack<T>, and algorithms like sort(), find(), and accumulate() are all implemented as templates.
C++ templates also support specialization (providing custom implementations for specific types), non-type parameters (passing values like integers as template arguments), and variadic templates (accepting any number of template arguments).
Why Does It Matter?
Why Do Templates Matter?
Templates are one of the most powerful features of C++. They enable generic programming, which is a paradigm complementary to object-oriented programming. Understanding templates is essential for using the STL effectively and for writing professional C++ code.
1. Code Reuse Without Runtime Overhead
When Arjun writes a template<typename T> T maxOf(T a, T b), the compiler generates optimized code for each type used. Unlike virtual functions (which have vtable overhead), templates are resolved entirely at compile time with zero runtime cost.
2. The STL Is Built on Templates
Every STL container (vector, map, set, queue), every algorithm (sort, find, transform), and every utility (pair, tuple, optional) is a template. You cannot use the STL without understanding templates.
3. Top Interview Topic for Product Companies
Companies like Google, Microsoft, Amazon, and Flipkart ask template questions in C++ interviews. Topics include template argument deduction, specialization, SFINAE (Substitution Failure Is Not An Error), and compile-time computation.
4. Type Safety Without Sacrifice
Before templates, C programmers used void* pointers for generic code, losing all type safety. Templates give you generic code that is fully type-checked at compile time. If Kavya accidentally passes a string where an int is expected, the compiler catches it immediately.
5. Compile-Time Computation
Templates can be used for compile-time computation (template metaprogramming). Factorial, Fibonacci, and type traits can all be computed at compile time, resulting in zero runtime cost. This is the basis of modern C++ libraries like <type_traits>.
Detailed Explanation
Detailed Explanation
1. Function Templates
A function template defines a blueprint for a function. The compiler generates a concrete function for each type used.
template<typename T>
T add(T a, T b) {
return a + b;
}
// Explicit instantiation:
int r1 = add<int>(3, 4); // 7
double r2 = add<double>(3.5, 4.5); // 8.0
// Implicit deduction (compiler deduces T):
int r3 = add(3, 4); // T deduced as int
double r4 = add(3.5, 4.5); // T deduced as doubleThe compiler deduces the template argument from the function arguments. If the arguments have different types (e.g., add(3, 4.5)), deduction fails and you must specify explicitly: add<double>(3, 4.5).
2. Multiple Template Parameters
A template can have multiple type parameters:
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
cout << multiply(3, 4.5) << endl; // 13.5 (int * double = double)
cout << multiply(2.0, 3) << endl; // 6.03. Class Templates
A class template defines a blueprint for a class. You must specify the template arguments explicitly when creating objects (prior to C++17 CTAD).
template<typename T>
class Stack {
T* data;
int top;
int capacity;
public:
Stack(int cap) : capacity(cap), top(-1) { data = new T[cap]; }
~Stack() { delete[] data; }
void push(T val) {
if (top < capacity - 1) data[++top] = val;
}
T pop() {
if (top >= 0) return data[top--];
throw runtime_error("Stack empty");
}
bool empty() const { return top == -1; }
int size() const { return top + 1; }
};
Stack<int> intStack(10);
intStack.push(42);
Stack<string> strStack(5);
strStack.push("Kavya");4. Non-Type Template Parameters
Template parameters can be values (integers, pointers, etc.), not just types:
template<typename T, int N>
class FixedArray {
T data[N];
public:
T& operator[](int i) { return data[i]; }
int size() const { return N; }
};
FixedArray<int, 5> arr; // Array of 5 ints, size known at compile time
FixedArray<double, 10> arr2; // Array of 10 doubles
// N must be a compile-time constant
// FixedArray<int, n> arr3; // Error if n is a runtime variableNon-type parameters must be compile-time constants. They enable compile-time computation and zero-overhead abstractions like std::array<T, N>.
5. Template Specialization
Full specialization provides a completely custom implementation for a specific type:
// Primary template
template<typename T>
class Printer {
public:
void print(T val) { cout << "Value: " << val << endl; }
};
// Full specialization for const char*
template<>
class Printer<const char*> {
public:
void print(const char* val) { cout << "String: \"" << val << "\"" << endl; }
};
// Full specialization for bool
template<>
class Printer<bool> {
public:
void print(bool val) { cout << "Bool: " << (val ? "true" : "false") << endl; }
};
Printer<int> p1; p1.print(42); // Value: 42
Printer<const char*> p2; p2.print("Ravi"); // String: "Ravi"
Printer<bool> p3; p3.print(true); // Bool: truePartial specialization specializes some template parameters while leaving others generic. It is only available for class templates, not function templates:
// Primary template
template<typename T, typename U>
class Pair {
public:
void info() { cout << "Generic Pair" << endl; }
};
// Partial specialization: both types are the same
template<typename T>
class Pair<T, T> {
public:
void info() { cout << "Same-type Pair" << endl; }
};
// Partial specialization: second type is int
template<typename T>
class Pair<T, int> {
public:
void info() { cout << "Pair with int second" << endl; }
};
Pair<double, string> p1; p1.info(); // Generic Pair
Pair<int, int> p2; p2.info(); // Same-type Pair
Pair<string, int> p3; p3.info(); // Pair with int second6. Function Template Specialization
Function templates can be fully specialized (but not partially specialized):
template<typename T>
void display(T val) {
cout << "Generic: " << val << endl;
}
template<>
void display<bool>(bool val) {
cout << "Bool: " << (val ? "true" : "false") << endl;
}
display(42); // Generic: 42
display(true); // Bool: true7. Variadic Templates (C++11)
Variadic templates accept any number of template arguments using a parameter pack:
// Base case: no arguments
void print() {
cout << endl;
}
// Recursive case: peel off first argument, recurse on rest
template<typename T, typename... Args>
void print(T first, Args... rest) {
cout << first << " ";
print(rest...); // Expand the pack
}
print(1, 2.5, "Arjun", 'A', true);
// Output: 1 2.5 Arjun A 1The ... is the pack expansion operator. Args... is a template parameter pack, and rest... is a function parameter pack.
8. auto and decltype
auto lets the compiler deduce the type of a variable. decltype gives you the type of an expression without evaluating it:
auto x = 42; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
int a = 5;
decltype(a) b = 10; // b is int (same type as a)
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
auto result = add(3, 4.5); // result is double (7.5)9. Default Template Arguments
Template parameters can have default values:
template<typename T = int, int N = 10>
class Buffer {
T data[N];
public:
int size() const { return N; }
};
Buffer<> b1; // T=int, N=10
Buffer<double> b2; // T=double, N=10
Buffer<char, 256> b3; // T=char, N=256
Code Examples
#include <iostream>
using namespace std;
template<typename T>
T maxOf(T a, T b) {
return (a > b) ? a : b;
}
template<typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
cout << maxOf(10, 20) << endl;
cout << maxOf(3.14, 2.71) << endl;
cout << maxOf(string("Arjun"), string("Kavya")) << endl;
int a = 5, b = 10;
swapValues(a, b);
cout << "After swap: " << a << ", " << b << endl;
string s1 = "Hello", s2 = "World";
swapValues(s1, s2);
cout << "After swap: " << s1 << ", " << s2 << endl;
return 0;
}T from the arguments. maxOf(10, 20) deduces T=int. maxOf(string, string) deduces T=string (uses lexicographic comparison). swapValues takes references and swaps in-place.#include <iostream>
#include <string>
using namespace std;
template<typename T>
class Stack {
T* data;
int top;
int capacity;
public:
Stack(int cap) : capacity(cap), top(-1) {
data = new T[cap];
}
~Stack() { delete[] data; }
void push(T val) {
if (top < capacity - 1) data[++top] = val;
else cout << "Stack full" << endl;
}
T pop() {
if (top >= 0) return data[top--];
throw runtime_error("Stack empty");
}
T peek() const {
if (top >= 0) return data[top];
throw runtime_error("Stack empty");
}
bool empty() const { return top == -1; }
int size() const { return top + 1; }
};
int main() {
Stack<int> intStack(5);
intStack.push(10);
intStack.push(20);
intStack.push(30);
cout << "Top: " << intStack.peek() << endl;
cout << "Pop: " << intStack.pop() << endl;
cout << "Size: " << intStack.size() << endl;
Stack<string> strStack(3);
strStack.push("Ravi");
strStack.push("Priya");
cout << "Top: " << strStack.peek() << endl;
return 0;
}Stack class template works with any type. Stack<int> creates a stack of integers, Stack<string> creates a stack of strings. The compiler generates separate class definitions for each instantiation.#include <iostream>
using namespace std;
template<typename T, int N>
class FixedArray {
T data[N];
public:
void set(int i, T val) {
if (i >= 0 && i < N) data[i] = val;
}
T get(int i) const {
if (i >= 0 && i < N) return data[i];
throw out_of_range("Index out of bounds");
}
int size() const { return N; }
};
template<int N>
int factorial() {
return N * factorial<N - 1>();
}
template<>
int factorial<0>() {
return 1;
}
int main() {
FixedArray<int, 5> arr;
for (int i = 0; i < 5; i++) arr.set(i, i * 10);
for (int i = 0; i < arr.size(); i++) cout << arr.get(i) << " ";
cout << endl;
FixedArray<double, 3> darr;
darr.set(0, 1.1); darr.set(1, 2.2); darr.set(2, 3.3);
for (int i = 0; i < darr.size(); i++) cout << darr.get(i) << " ";
cout << endl;
cout << "5! = " << factorial<5>() << endl;
cout << "10! = " << factorial<10>() << endl;
return 0;
}FixedArray<int, 5> creates an array of exactly 5 ints, with size known at compile time. factorial<5>() computes 5! at compile time using template recursion with a base case specialization for N=0.#include <iostream>
#include <string>
using namespace std;
// Primary template
template<typename T>
class TypeName {
public:
static string name() { return "unknown"; }
};
// Full specializations
template<> class TypeName<int> {
public:
static string name() { return "int"; }
};
template<> class TypeName<double> {
public:
static string name() { return "double"; }
};
template<> class TypeName<string> {
public:
static string name() { return "string"; }
};
// Partial specialization for pointers
template<typename T>
class TypeName<T*> {
public:
static string name() { return "pointer to " + TypeName<T>::name(); }
};
int main() {
cout << TypeName<int>::name() << endl;
cout << TypeName<double>::name() << endl;
cout << TypeName<string>::name() << endl;
cout << TypeName<char>::name() << endl;
cout << TypeName<int*>::name() << endl;
cout << TypeName<double*>::name() << endl;
return 0;
}int, double, and string. The partial specialization TypeName<T*> matches any pointer type and recursively uses the base type's name. char falls through to the primary template ("unknown").#include <iostream>
using namespace std;
// Base case
void print() {
cout << endl;
}
// Recursive variadic template
template<typename T, typename... Args>
void print(T first, Args... rest) {
cout << first;
if (sizeof...(rest) > 0) cout << ", ";
print(rest...);
}
// Variadic sum
template<typename T>
T sum(T val) {
return val;
}
template<typename T, typename... Args>
T sum(T first, Args... rest) {
return first + sum(rest...);
}
int main() {
print(1, 2.5, "Arjun", 'A', true);
print("Hello", "World");
cout << "Sum: " << sum(1, 2, 3, 4, 5) << endl;
cout << "Sum: " << sum(1.5, 2.5, 3.0) << endl;
return 0;
}print function accepts any number of arguments of any types. It peels off the first argument, prints it, and recurses on the rest. sizeof...(rest) returns the number of remaining arguments. The sum function adds all arguments together.#include <iostream>
#include <string>
using namespace std;
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
template<typename T, typename U>
auto multiply(T a, U b) {
return a * b; // C++14: return type deduced
}
int main() {
auto r1 = add(3, 4.5); // double
auto r2 = add(string("Hello, "), string("World"));
auto r3 = multiply(3, 4); // int
auto r4 = multiply(2.5, 4); // double
cout << r1 << endl;
cout << r2 << endl;
cout << r3 << endl;
cout << r4 << endl;
// decltype usage
int x = 10;
decltype(x) y = 20; // y is int
decltype(x + 0.5) z = 3.14; // z is double
cout << y << " " << z << endl;
return 0;
}auto and decltype enable type deduction. The trailing return type -> decltype(a + b) tells the compiler to deduce the return type from the expression a + b. In C++14, the return type can be deduced automatically without a trailing return type.Common Mistakes
Mismatched Types in Template Argument Deduction
template<typename T>
T maxOf(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << maxOf(3, 4.5); // Error: T deduced as int AND double
}// Option 1: Explicit template argument
cout << maxOf<double>(3, 4.5);
// Option 2: Two template parameters
template<typename T, typename U>
auto maxOf(T a, U b) -> decltype(a > b ? a : b) {
return (a > b) ? a : b;
}
cout << maxOf(3, 4.5); // OKT is used for both arguments, both arguments must deduce the same type. Either specify the type explicitly or use two separate template parameters.Forgetting template<> in Full Specialization
template<typename T>
void display(T val) {
cout << val << endl;
}
// Missing template<>
void display(bool val) { // This is an overload, not a specialization!
cout << (val ? "true" : "false") << endl;
}template<typename T>
void display(T val) {
cout << val << endl;
}
template<> // Required for specialization!
void display<bool>(bool val) {
cout << (val ? "true" : "false") << endl;
}template<> prefix. Without it, you create an overload rather than a specialization. Overloads and specializations have different rules for resolution.Using Runtime Variables as Non-Type Template Parameters
template<int N>
void printN() {
cout << N << endl;
}
int main() {
int n;
cin >> n;
printN<n>(); // Error: n is not a compile-time constant!
}template<int N>
void printN() {
cout << N << endl;
}
int main() {
constexpr int n = 10; // Compile-time constant
printN<n>(); // OK
printN<42>(); // OK
}constexpr, const initialized with literals, or literal values). Runtime values cannot be template arguments because templates are resolved at compile time.Defining Template Member Functions in .cpp Files
// stack.h
template<typename T>
class Stack {
T* data;
public:
Stack(int n);
void push(T val);
};
// stack.cpp
#include "stack.h"
template<typename T>
Stack<T>::Stack(int n) { data = new T[n]; }
template<typename T>
void Stack<T>::push(T val) { /* ... */ }
// main.cpp
#include "stack.h"
Stack<int> s(10); // Linker error: undefined reference// stack.h (put everything in the header)
template<typename T>
class Stack {
T* data;
public:
Stack(int n) { data = new T[n]; }
void push(T val) { /* ... */ }
};
// main.cpp
#include "stack.h"
Stack<int> s(10); // OK: compiler sees full definitionPartial Specialization of Function Templates
// Trying to partially specialize a function template
template<typename T, typename U>
void process(T a, U b) {
cout << "Generic" << endl;
}
// Partial specialization: U is int
template<typename T>
void process<T, int>(T a, int b) { // Error!
cout << "U is int" << endl;
}// Use overloading instead of partial specialization
template<typename T, typename U>
void process(T a, U b) {
cout << "Generic" << endl;
}
// Overload with second parameter as int
template<typename T>
void process(T a, int b) {
cout << "U is int" << endl;
}
process(3.14, "hello"); // Generic
process(3.14, 42); // U is intint.Summary
- Templates are a compile-time mechanism for generic programming. The compiler generates type-specific code (instantiations) from templates with zero runtime overhead.
- Function templates use template<typename T> before the function. The compiler can deduce T from arguments (implicit deduction) or you can specify it explicitly (explicit instantiation).
- Class templates use template<typename T> before the class. Prior to C++17, you must specify template arguments explicitly when creating objects: Stack<int> s(10).
- Non-type template parameters (template<int N>) accept compile-time constant values. They enable fixed-size arrays, compile-time factorial, and zero-overhead abstractions.
- Full specialization (template<>) provides a custom implementation for a specific type. Both function and class templates support full specialization.
- Partial specialization specializes some template parameters while leaving others generic. Only class templates support partial specialization; function templates use overloading instead.
- Variadic templates (template<typename... Args>) accept any number of arguments. They use recursive peeling (process first, recurse on rest) with a base case for zero arguments.
- auto deduces variable types from initializers. decltype gives the type of an expression. Trailing return types (-> decltype(a+b)) combine with templates for generic return types.
- Template definitions must be in header files because the compiler needs the full definition at the point of instantiation. Separating into .h and .cpp causes linker errors.
- The entire C++ STL (vector, map, sort, find, etc.) is built on templates. Understanding templates is prerequisite for effective STL usage.