Chapter 18 Advanced 55 min min read Updated 2026-04-10

Templates - Function and Class Templates

Practice Questions →

In This Chapter

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;          // Arjun

Templates 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 double

The 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.0

3. 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 variable

Non-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: true

Partial 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 second

6. 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: true

7. 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 1

The ... 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

Function Template with Type Deduction
#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;
}
The compiler deduces 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.
20 3.14 Kavya After swap: 10, 5 After swap: World, Hello
Class Template: Generic Stack
#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;
}
The 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.
Top: 30 Pop: 30 Size: 2 Top: Priya
Non-Type Template Parameters
#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.
0 10 20 30 40 1.1 2.2 3.3 5! = 120 10! = 3628800
Template Specialization (Full and Partial)
#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;
}
Full specializations provide custom implementations for 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").
int double string unknown pointer to int pointer to double
Variadic Templates
#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;
}
The 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.
1, 2.5, Arjun, A, 1 Hello, World Sum: 15 Sum: 7
auto, decltype, and Trailing Return Types
#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.
7.5 Hello, World 12 10 20 3.14

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
}
Compilation error: deduced conflicting types for T. 3 deduces int, 4.5 deduces 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);  // OK
When a single template parameter T 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;
}
This creates a function overload, not a template specialization. Both exist and overload resolution picks the non-template version for bool. This may seem to work but has different behavior with template argument deduction.
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 full specialization requires the 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!
}
Compilation error: non-type template argument is not a constant expression. Template parameters must be known at compile time.
template<int N>
void printN() {
    cout << N << endl;
}

int main() {
    constexpr int n = 10;  // Compile-time constant
    printN<n>();  // OK
    printN<42>(); // OK
}
Non-type template parameters must be compile-time constants (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
Linker error. The compiler needs to see the full template definition when instantiating. If the definition is in a .cpp file, main.cpp cannot see it.
// 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 definition
Template definitions must be visible to the compiler at the point of instantiation. In practice, this means template code goes in header files, not .cpp files. This is a fundamental difference from non-template code.

Partial 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;
}
Compilation error: function templates cannot be partially specialized. Only class templates support partial specialization.
// 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 int
C++ does not allow partial specialization of function templates. Use function overloading instead. Overload resolution will pick the more specific version when the second argument is int.

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.

Ready to Practice?

Test your understanding with 50+ practice questions on this topic.

Go to Practice Questions

Want to learn C++ with a live mentor?

Explore our C++ Masterclass