CSC166 Object Oriented Programming

Object Oriented ProgrammingUnit 810 min read

Templates & Generic Programming: Reusable Code Design

Unit 8 of Object Oriented Programming covers templates (function/class), generic programming, and type-independent code in C++. Learn how to write flexible, reusable components (e.g., containers, algorithms) that work with any data type without code duplication, with real-world examples from Nepalese apps and traced ex

TAKEAWAYS:

  • Templates let you write one function/class that works for multiple data types (e.g., swap<T>, vector<T>).
  • Function templates are declared with template <typename T> and instantiated at compile-time.
  • Class templates create families of classes (e.g., Stack<int>, Stack<string>) with identical logic but different types.
  • Generic programming avoids code duplication by parameterizing types (e.g., STL containers like list<T>, map<K,V>).
  • Type deduction (auto and decltype) simplifies template usage in modern C++.
  • Common pitfalls: forgetting angle brackets <>, mixing templates with pointers, or overusing them for non-generic code.

1. Why Templates? The Problem They Solve

1.1 The "Copy-Paste" Nightmare

Before templates, if you wrote a swap function for int, you had to rewrite it for double, string, etc.:

void swapInt(int &a, int &b) { int temp = a; a = b; b = temp; }
void swapDouble(double &a, double &b) { double temp = a; a = b; b = temp; }

Solution: Templates let you write one swap that works for any type:

template <typename T>
void swap(T &a, T &b) { T temp = a; a = b; b = temp; }

Visual: How swap<T> works for int vs. string:

flowchart TD
    A["swap<int>(x, y)"] -->|"Compile-time"| B["T = int\nT temp = x;\nx = y;\ny = temp;"]
    C["swap<string>(s1, s2)"] -->|"Compile-time"| D["T = string\nstring temp = s1;\ns1 = s2;\ns2 = temp;"]

1.2 Real-World Analogy: Kathmandu Traffic Routes

Imagine Kathmandu’s traffic system:

  • Without templates: Separate "routes" for buses, cars, and motorcycles (duplicate code).
  • With templates: One Route<T> class that works for any vehicle type, reducing errors and maintenance.

2. Function Templates: Writing Generic Functions

2.1 Syntax and Instantiation

A function template defines a family of functions parameterized by a type T:

template <typename T>  // or: template <class T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

How it works:

  1. Declaration: template <typename T> tells the compiler to generate a function for any type T.
  2. Instantiation: When you call max(3, 5) or max("hello", "world"), the compiler creates a new version of max for int and const char*.

Visual: Compile-time instantiation:

flowchart TD
    A["max(3, 5)"] --> B["T = int\nint max(int, int) { ... }"]
    C["max(\"a\", \"b\")"] --> D["T = const char*\nconst char* max(const char*, const char*) { ... }"]

2.2 Worked Example: Generic printArray

Task: Write a function to print any array (of int, double, string, etc.). Solution:

#include <iostream>
template <typename T>
void printArray(T arr[], int size) {
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
}

Trace:

Step T Type arr Content Output
1 int {1, 2, 3} 1 2 3
2 double {1.1, 2.2} 1.1 2.2
3 string {"a", "b"} a b

Real-World Tie-In:

  • eSewa’s Order Queue: Imagine a generic Queue<T> template used to manage orders for electricity bills, license renewals, or vehicle registration. The same queue logic works for all types.

3. Class Templates: Generic Data Structures

3.1 Defining a Class Template

A class template creates a family of classes (e.g., Stack<int>, Stack<string>):

template <typename T>
class Stack {
private:
    T *arr;
    int top;
    int capacity;
public:
    Stack(int size) { ... }
    void push(T x) { ... }
    T pop() { ... }
};

Visual: Stack<T> for int vs. string:

flowchart TD
    A["Stack<int> s1;"] --> B["T = int\nint* arr;"]
    C["Stack<string> s2;"] --> D["T = string\nstring* arr;"]

3.2 Worked Example: Generic Stack Implementation

Code:

template <typename T>
class Stack {
private:
    T *arr;
    int top;
public:
    Stack(int size) {
        arr = new T[size];
        top = -1;
    }
    void push(T x) {
        arr[++top] = x;
    }
    T pop() {
        return arr[top--];
    }
};

Trace: Pushing int and string:

Operation Stack<int> State Stack<string> State
push(5) arr = [5], top = 0 —
push("hi") — arr = ["hi"], top = 0

Real-World Tie-In:

  • Pathao’s Ride Queue: A Queue<RideRequest> template manages ride requests for bikes, cars, and auto-rickshaws. The same queue logic applies to all request types.

4. Template Specialization: Customizing for Specific Types

4.1 Why Specialize?

Sometimes, the generic version doesn’t work for all types (e.g., std::string needs special handling). Example: Override max for std::string to compare lengths:

template <>
const char* max<const char*>(const char* a, const char* b) {
    return (strlen(a) > strlen(b)) ? a : b;
}

Visual: Specialization vs. generic:

flowchart TD
    A["max(\"hello\", \"world\")"] --> B["Generic:\nCompare chars\nResult: \"world\""]
    C["max<const char*>()"] --> D["Specialized:\nCompare lengths\nResult: \"hello\""]

4.2 Partial Specialization (Advanced)

For complex types (e.g., pairs), you can specialize partially:

template <typename T1, typename T2>
class Pair {
    // Generic implementation
};

template <typename T>
class Pair<T, T> {  // Specialization for same types
    // Custom logic
};

5. Generic Programming with STL

5.1 The Standard Template Library (STL)

STL uses templates heavily for containers (vector<T>, map<K,V>) and algorithms (sort, find). Example: std::vector works for any type:

#include <vector>
std::vector<int> nums = {1, 2, 3};
std::vector<std::string> names = {"Alice", "Bob"};

5.2 Worked Example: Generic sort

Code:

#include <algorithm>
template <typename RandomIt>
void sort(RandomIt first, RandomIt last) {
    // Implementation uses template metaprogramming
}

Trace: Sorting int and string:

Input Output (sort)
{3, 1, 2} {1, 2, 3}
{"banana", "apple"} {"apple", "banana"}

Real-World Tie-In:

  • NEPSE Stock Data: A map<string, double> template stores stock symbols (e.g., "NTC", "NBL") and their prices, sorted alphabetically or by value.

6. Type Deduction: auto and decltype

6.1 auto in Templates

Simplifies template usage by letting the compiler deduce types:

template <typename T>
auto add(T a, T b) -> decltype(a + b) {
    return a + b;
}

Example:

auto x = add(3, 5);       // x is int
auto y = add(1.1, 2.2);   // y is double

6.2 decltype

Returns the type of an expression (used in templates for return types):

template <typename T>
decltype(T{} + T{}) add(T a, T b) {
    return a + b;
}

7. Common Pitfalls and Best Practices

7.1 Pitfalls

Mistake Fix
Forgetting <> in calls Stack<int> s; (not Stack<int> s())
Mixing templates with pointers Use T* or std::shared_ptr<T>
Overusing templates Prefer non-templates for simple cases

7.2 Best Practices

  • Use typename: Always use typename T (not class T) in templates.
  • Document templates: Clearly state which types a template supports.
  • Prefer STL: Use vector<T>, map<K,V> instead of reinventing wheels.

In the Real World

  1. Khalti’s Transaction Logs:

    • Uses vector<Transaction> where Transaction is a struct with string userID, double amount, and string status.
    • The same vector logic works for deposits, transfers, and withdrawals.
  2. Daraz’s Order Processing:

    • A Queue<Order> template manages orders for electronics, groceries, and fashion.
    • The queue’s enqueue() and dequeue() methods work identically for all product types.
  3. NTC’s Network Packet Handling:

    • A map<IPAddress, Packet> template routes packets to different devices.
    • The map automatically sorts IPs, optimizing lookup time.

Exam Tip

What Examiners Look For

  1. Syntax: Know the exact syntax for:

    • Function templates: template <typename T> retType func(T param);
    • Class templates: template <typename T> class Name { ... };
    • Template instantiation: Stack<string> s;
  2. Instantiation: Explain how T is replaced with actual types at compile-time.

  3. STL Connection: Relate templates to STL containers/algorithms (e.g., vector<T>, sort).

  4. Specialization: Know when and how to specialize templates (e.g., for std::string).

  5. Pitfalls: Be ready to spot errors like:

    • Missing <> in template calls.
    • Incorrect type deduction.

Common Exam Questions

  • "How does a function template differ from a regular function?" → Templates generate multiple functions at compile-time; regular functions are fixed.

  • "Write a class template for a Queue<T>." → Include enqueue(T), dequeue(), and a T* arr member.

  • "Explain template specialization with an example." → Show how to override max for std::string to compare lengths.

Marks Distribution

Topic Marks (Approx.)
Function templates 2–3
Class templates 3–4
STL and templates 2
Specialization 2
Pitfalls/Code 3

Final Note: Templates are compile-time magic—they let you write one piece of code that works for many types. Master the syntax, understand instantiation, and always tie examples to real-world apps (like Khalti, Daraz, or NTC). Practice writing Stack<T>, Queue<T>, and generic functions to ace this unit!

Based on the TU BSc CSIT syllabus for Object Oriented Programming (CSC166), unit 8.

Discussion

Loading…