Course Topics
C++ Basics Introduction and Setup C++ vs C Differences Syntax and Program Structure Compiling and Running C++ Programs Exercise Variables and Data Types Variables and Declaration Data Types (int, float, char, double, bool) Constants and Literals Type Conversion and Casting Auto Keyword Exercise Operators Arithmetic Operators Comparison Operators Logical Operators Assignment Operators Bitwise Operators Exercise Input and Output Standard Input/Output (cin, cout) Stream Manipulators File Input/Output String Streams Exercise Control Flow - Conditionals If Statements If-Else Statements Switch Statements Nested Conditionals Exercise Control Flow - Loops For Loops (including range-based) While Loops Do-While Loops Loop Control (break, continue) Nested Loops Exercise Functions Function Declaration and Definition Function Parameters and Arguments Return Statements and Types Function Overloading Default Parameters Exercise Arrays and Vectors Arrays (Static and Dynamic) Multi-Dimensional Arrays Introduction to Vectors Vector Operations and Methods Exercise Pointers and References Introduction to Pointers Pointer Arithmetic Pointers and Arrays References vs Pointers Smart Pointers (unique_ptr, shared_ptr) Exercise Strings String Class String Operations and Methods C-Style Strings vs String Class String Manipulation Exercise Object-Oriented Programming - Classes Classes and Objects Data Members and Member Functions Constructors and Destructors Access Specifiers (private, public, protected) Exercise Object-Oriented Programming - Advanced Inheritance (Single, Multiple, Multilevel) Polymorphism and Virtual Functions Abstract Classes and Pure Virtual Functions Operator Overloading Exercise Templates Function Templates Class Templates Template Specialization Template Parameters Exercise Standard Template Library (STL) Containers (vector, list, map, set) Iterators Algorithms STL Functions Exercise Exception Handling Try-Catch Blocks Exception Types Throwing Custom Exceptions Exception Safety Exercise File Handling File Streams (ifstream, ofstream, fstream) Reading from Files Writing to Files Binary File Operations Exercise Memory Management Dynamic Memory Allocation (new, delete) Memory Leaks and Management RAII (Resource Acquisition Is Initialization) Smart Pointers in Detail Exercise Modern C++ Features Lambda Expressions Move Semantics and R-value References Range-based For Loops nullptr and constexpr Exercise Advanced Topics Namespaces Preprocessor Directives Header Files and Libraries Design Patterns in C++ Exercise Final Project Project Planning and Design Building Complete Application Code Organization and Best Practices Testing and Debugging Exercise

Class Templates

Introduction

Class templates are an essential feature of the C++ programming language that allow you to create generic classes or functions which can work with different data types. Although C does not support class templates natively, they are a fundamental concept for any C++ developer and can be applied indirectly in C through some workarounds. In this guide, we will discuss how to leverage class templates using the C++ subset within C projects.

Why this topic matters in C programming

Class templates offer several benefits in terms of code reusability, flexibility, and efficiency. By creating generic classes that can work with multiple data types, you can write less repetitive code and improve the maintainability of your programs. Furthermore, understanding class templates is essential for transitioning from C to C++ or working on projects that require a combination of both languages.

What you'll learn and how it fits into C development

In this tutorial, we will cover the fundamentals of class templates in C++, including:

  • Key terminology and concepts related to class templates
  • Writing generic classes using class templates
  • Instantiating class templates with various data types
  • Common pitfalls and solutions when working with class templates in C++

Real-world applications

Class templates are widely used in software development for creating generic algorithms, containers, and other reusable components. By understanding how to use class templates, you will be better equipped to write efficient and maintainable code for various projects, including game engines, operating systems, and libraries.

Core Concepts

Class templates are a mechanism for defining parameterized classes or functions that can operate on multiple data types. The parameters are defined within angle brackets (e.g., <T>) and represent the type or types that will be used when instantiating the template. Here's an example of a simple class template for a container:

// Example of a basic class template for a container in C++
template <typename T>
class Container {
public:
    Container() : data(nullptr), size(0) {}
    ~Container() { delete[] data; }

    void push_back(const T& value) {
        if (size >= capacity) {
            reserve(capacity * 2);
        }
        data[size++] = value;
    }

    size_t size() const { return size; }
    const T& operator[](size_t index) const { return data[index]; }
    T& operator[](size_t index) { return data[index]; }

private:
    T* data;
    size_t capacity, size;

    void reserve(size_t newCapacity) {
        T* temp = new T[newCapacity];
        if (data) {
            for (size_t i = 0; i < size; ++i) {
                temp[i] = data[i];
            }
            delete[] data;
        }
        data = temp;
        capacity = newCapacity;
    }
};

In this example, T is a placeholder for any valid C++ data type that you want to use with the container. To create an instance of this class template and use it with integers, you would write:

Container<int> myIntContainer; // Instantiate Container template with int as T
myIntContainer.push_back(42);   // Add an integer to the container
std::cout << myIntContainer[0] << std::endl;  // Print the first element of the container

Practical Examples

In this section, we will explore various practical examples using class templates in C++.

Example 1: A generic Stack Class Template

Here's an example of a stack class template that can work with any data type for which the push(), pop(), and size() functions are defined:

// Example of a generic Stack class template in C++
template <typename T>
class Stack {
public:
    void push(const T& value) {
        if (size == capacity) {
            reserve(capacity * 2);
        }
        data[size++] = value;
    }

    T pop() {
        if (isEmpty()) {
            throw std::runtime_error("Cannot pop from an empty stack.");
        }
        return data[--size];
    }

    size_t size() const { return size; }
    bool isEmpty() const { return size == 0; }

private:
    T* data;
    size_t capacity, size;

    void reserve(size_t newCapacity) {
        T* temp = new T[newCapacity];
        if (data) {
            for (size_t i = 0; i < size; ++i) {
                temp[i] = data[i];
            }
            delete[] data;
        }
        data = temp;
        capacity = newCapacity;
    }
};

Example 2: Using the Stack Template with Different Data Types

Now that we have a generic stack class template, let's use it with different data types:

#include <iostream>
#include <vector>

using namespace std;

// Instantiate Stack template with int as T
Stack<int> intStack;

void fillIntStack() {
    intStack.push(42);
    intStack.push(10);
    intStack.push(37);
}

// Instantiate Stack template with double as T
Stack<double> doubleStack;

void fillDoubleStack() {
    doubleStack.push(3.14);
    doubleStack.push(2.718);
    doubleStack.push(e); // Magic constant for e (approximately 2.71828)
}

int main() {
    fillIntStack();
    cout << "Int Stack:\n";
    while (!intStack.isEmpty()) {
        cout << intStack.pop() << endl;
    }

    fillDoubleStack();
    cout << "\nDouble Stack:\n";
    while (!doubleStack.isEmpty()) {
        cout << doubleStack.pop() << endl;
    }

    return 0;
}

Common Issues and Solutions (CRITICAL SECTION)

Compilation Error: Template Argument Does Not Name a Valid Type

What causes it:

// Bad C code example that triggers the error
template <int T>
class MyClass { /* ... */ }; // int is not a valid type for templates

Error message:

error: invalid use of incomplete type 'std::integral_constant<int, T>'

Solution:

Use a valid C++ data type as the template argument (e.g., int, float, double, char, or user-defined types).

Why it happens: Class templates require valid data types to be used as template arguments, and C++ primitive types like int and float are allowed.

How to prevent it: Make sure you use a valid C++ data type (or user-defined type) when instantiating class templates.

Compilation Error: Undefined Reference to Function Template

What causes it:

// Bad C code example that triggers the error
template <typename T>
void myFunction(const T& value); // Declare function template, but don't define it

int main() {
    myFunction<int>(42);  // Instantiate and call the function template with int
}

Error message:

error: undefined reference to 'myFunction<int>'

Solution:

Define the function template body before or after its declaration.

Why it happens: Function templates are not instantiated until they are called with specific arguments, so you must define their bodies for them to work correctly.

How to prevent it: Define the function template body before or after its declaration. If you separate the declaration and definition, make sure to put the definition in a header file (with #ifndef and #define guard statements).

Segmentation Fault: Accessing Out-of-Bounds Memory

What causes it:

// Bad C code example that triggers the error
template <typename T>
class Stack {
public:
    void push(const T& value) {
        data[size++] = value; // Accessing out-of-bounds memory if size > capacity
    }

    // ...
};

Error message:

Runtime error: Segmentation fault (core dumped)

Solution:

Use appropriate memory management techniques, such as reserving enough space for the data and expanding the capacity of the container when necessary.

Why it happens: Accessing out-of-bounds memory can lead to segmentation faults, which occur when your program attempts to read or write beyond the allocated memory.

How to prevent it: Use a combination of size and capacity variables to manage the memory usage of your container. When adding an element and size reaches capacity, expand the capacity by reserving more memory.

Compilation Error: Ambiguous Template Instantiation

What causes it:

// Bad C code example that triggers the error
template <typename T>
void myFunction(const T& value); // Declare function template, but don't define it

template <typename U>
class MyClass {
public:
    void myFunction(const U& value) {} // Overloaded function with a different parameter type
};

int main() {
    MyClass<int> obj;
    obj.myFunction<float>(3.14);  // Instantiate and call the overloaded function with float
}

Error message:

error: ambiguous template instantiation for 'myFunction' (with 'T=float' and 'U=int')

Solution:

Either define the function template body to handle all possible template arguments or provide separate overloads for each data type you want to support.

Why it happens: When you have multiple templates with overlapping types, the compiler may not be able to determine which one to use.

How to prevent it: Define a single function template that can handle all possible template arguments, or provide separate overloads for each data type you want to support.

Compilation Error: No Instance of Overloaded Function matches the argument list

What causes it:

// Bad C code example that triggers the error
template <typename T>
void myFunction(const T& value); // Declare function template, but don't define it

int main() {
    myFunction<float>(3.14);  // Instantiate and call the function template with float
}

Error message:

error: no instance of function template 'myFunction' matches the argument list  expected 1 arguments, but 0 were provided

Solution:

Modify the function template to accept the required number and types of arguments.

Why it happens: The function template you declared does not match the actual call in the code.

How to prevent it: Ensure that the function template signature matches the arguments provided when calling the function. If necessary, adjust the template parameters or add additional overloads for different argument lists.

Best Practices

  • Use header files (.h or .hpp) to share class templates across multiple source files.
  • Include appropriate standard C++ libraries (e.g., <vector>, <memory>, <iostream>) when needed.
  • Implement memory management techniques, such as reserving and expanding the capacity of containers.
  • Use exception handling to manage errors gracefully.
  • Follow coding standards, such as Google's C++ Style Guide (https://google.github.io/styleguide/cppguide.html) or the C++ Core Guidelines (https://isocpp.org/wiki/std/cppcoreguidelines).

Key Takeaways

  • Class templates allow you to create generic classes and functions that can work with multiple data types in C++.
  • Understanding class templates is essential for transitioning from C to C++ or working on projects that require a combination of both languages.
  • Use proper memory management techniques, such as reserving and expanding the capacity of containers.
  • Follow coding standards and best practices when working with class templates.
  • Familiarize yourself with common errors and solutions related to class templates in C++.