⚙️ C++

Generics und parametrische Typen in C++: Eine ausführliche Anleitung

Here is the translated HTML article in English:

What is it and why? (a real-life analogy)

Imagine you are the owner of a small warehouse. Different people come to you and ask to store their things. One brings a box of books, another brings a crate of tools, a third brings a sack of apples. What will you do? Build a separate warehouse for books, a separate one for tools, and a separate one for apples? That would be very inconvenient and expensive.

Instead, you build one universal warehouse that can store anything. You simply say: "Bring your things, I'll put them on a shelf. I don't care what they are — books, tools, or apples. The main thing is that you later take back the same thing you brought." This is the idea of generics (generic types) in programming.

In C++, generics allow us to write one class or function that can work with any data type: integers, floats, strings, your own classes — anything. Instead of writing a separate function for adding integers, a separate one for adding floats, and a separate one for adding strings (which would be a nightmare), we write one generic function that can add anything that can be added.

Important: Generics do not erase types (as in Java or Python), but create a new version of the function or class for each type you use them with. This is called templates in C++. The compiler itself generates the necessary code.

Let's look at a real-life example. You have a box. You can put an apple in it, and then it's an "apple box." Or you can put a book in it, and then it's a "book box." But the box itself is the same. In C++, we would write Box<Apple> or Box<Book>. The angle brackets are the way to tell the compiler: "Make me a box that will store exactly apples."

Theory: how it works internally

To understand generics, you need to look "under the hood" of the compiler. When you write:

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

The compiler does not create any code immediately. It just remembers: "I have a template for the max function." When you write in the code:

int result = max(5, 10);

The compiler looks: "Aha, the argument types are int. So I need to create a max function where T = int." And it generates this code:

int max(int a, int b) {
    return (a > b) ? a : b;
}

If you later write:

double result2 = max(3.14, 2.71);

The compiler will generate another function:

double max(double a, double b) {
    return (a > b) ? a : b;
}

This is called template instantiation. Each time you use a template with a new type, the compiler creates a separate copy of the function or class for that type.

Important: This differs from how generics work in Java or C#. There, the compiler creates only one version of the code, and type checks happen at compile time. In C++, each version is a separate, fully compiled piece of code. This gives maximum performance but can increase the size of the executable file.

The parameter typename T (you can also write class T — there is no difference) is a type parameter. You can imagine that T is a "hole" into which the real type is substituted at compile time. T can be anything: int, double, string, your own class — anything that supports the operations you use inside the template.

If inside the template you write a > b, then type T must support the > operator. If you write a + b, then T must support the + operator. If you call the method a.someMethod(), then T must have that method. The compiler will check this at the moment of template instantiation and will issue an error if the type is not suitable.

Syntax in C++: all forms and variants

In C++, there are several ways to declare templates. Let's break them all down.

1. Function template

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

// You can use multiple type parameters
template <typename T1, typename T2>
void printPair(T1 first, T2 second) {
    std::cout << "(" << first << ", " << second << ")" << std::endl;
}

// You can specify the return type separately
template <typename T>
auto getValue(T value) -> decltype(value) {
    return value;
}

2. Class template

// Declaring a template class
template <typename T>
class Box {
private:
    T content;  // Field of type T
public:
    void put(const T& item) {
        content = item;
    }
    
    T get() const {
        return content;
    }
};

// Usage
Box<int> intBox;       // Box for integers
Box<std::string> strBox; // Box for strings

3. Template with value parameters

A template parameter can be not only a type, but also a value (number, pointer, enumeration):

// Template with an integer parameter
template <typename T, int Size>
class Array {
private:
    T data[Size];  // Static array of fixed size
public:
    int getSize() const { return Size; }
    
    T& operator[](int index) {
        return data[index];
    }
};

// Usage
Array<double, 10> tenDoubles;  // Array of 10 floating-point numbers