programacion-generica-templates

What are Generics and Templates in Programming

  • 7 min

Templates and generics are mechanisms of generic programming that allow defining functions, classes, or structures that can operate with any data type.

If you’ve ever found yourself copying and pasting an entire function just to change int to float in the parameter definitions, then you’ve felt the pain we’re here to solve in today’s topic.

Although there are technical differences in how Templates and Generics work, the concept is the same. Writing code that works with types we don’t yet know.

programacion-genericos

It’s like designing the blueprint for a box without yet deciding what we’re going to store inside, or a function that changes to adapt to what you pass to it.

At first they can be intimidating, especially when you see complex definitions like <T, U, V> where T : new(). But they are a fundamental tool for building libraries and frameworks.

Why should you use them?

  1. Type Safety: The compiler detects errors before you run the program. If you try to put an Apple in a Box<Pear>, the code doesn’t compile.
  2. Performance: We avoid Boxing/Unboxing (converting types to Object and vice versa), which is computationally expensive.
  3. Clean Code: We maintain a single version of the algorithm, making maintenance and readability easier.

The Problem: Code Duplication

Imagine we want to create a simple function to swap two values between two variables (Swap).

In a strongly typed language (without generics), we would have to do this:

// Version for integers
void Swap(int a, int b) { ... }

// Version for floats
void Swap(float a, float b) { ... }

// Version for strings
void Swap(string a, string b) { ... }

Copied!

This is terrible. The algorithm is identical in all three cases, only the data type changes. If we find a bug in the logic, we have to fix it in three places (or in 27).

The Solution: Types as Parameters

Generic programming allows us to define a function (or a class) where the data type is one more parameter.

Just as a function receives arguments as values (x = 5), a generic function receives type arguments (T = int).

The T (for Type) is simply a widely used convention, but you could use any name. The syntax usually uses the < > symbols.

// Generic Pseudo-code
class Box<T>
{
    private T content;

    public void Store(T object) {
        this.content = object;
    }

    public T TakeOut() {
        return this.content;
    }
}

Copied!

Now we can reuse our logic infinitely:

  • Box<int>: A box that only stores integers.
  • Box<String>: A box that only stores text.
  • Box<User>: A box that stores user objects.

Templates or Generics? Are they the same?

Conceptually, basically yes, they are the same. Although the internal implementation is different.

In C++, they are called Templates. They work like an intelligent photocopier at compile time.

If you use List<int> and List<float>, the compiler generates literally two copies of the class code, replacing T with int in one and with float in the other.

  • Advantage: Extreme performance (optimized for each specific type).
  • Disadvantage: The final executable grows (Code Bloat) and compilation times are slow.

In languages like Java or C#, they are called Generics.

  • Java uses Type Erasure: It erases types at compile time. At runtime, everything is basically an Object, and it does automatic casts. It’s a compiler trick to ensure types.
  • C# uses Reified Generics: The type actually exists at runtime (List<int> is different from List<string> in memory). It’s more powerful and faster than Java’s approach.

Constraints

Generics are a very powerful tool, but we usually need to “constrain” them in some way to make them useful. If T can be anything, then I can’t do much with it.

For example, I can’t do a + b inside a generic function, because… what if T is a database connection? You can’t add connections.

To solve this, we use Constraints. We tell the compiler:

I accept any type T, as long as T meets these conditions

Depending on the language, the syntax varies (where, extends, concepts), but the idea is:

// I only accept types that are "Comparable" to each other
class Sorter<T> where T : IComparable
{
    public T Greater(T a, T b)
    {
        // Since I know T is IComparable, I can use the CompareTo method
        if (a.CompareTo(b) > 0) return a;
        return b;
    }
}

Copied!

This gives us the best of both worlds:

  • Flexibility, works with many types
  • Safety, we know which methods we can call

Syntax of Templates and Generics in Different Languages

Although the concept is similar, the syntax varies by language. Below, we’ll see examples in several popular languages.

In C++, templates are used to define generic functions and classes.

#include <iostream>

// Template function to swap values
template <typename T>
void swap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5, y = 10;
    swap(x, y);
    std::cout << "x: " << x << ", y: " << y << std::endl;  // Output: x: 10, y: 5

    std::string s1 = "Hello", s2 = "World";
    swap(s1, s2);
    std::cout << "s1: " << s1 << ", s2: " << s2 << std::endl;  // Output: s1: World, s2: Hello

    return 0;
}
Copied!

In Java, generics are used to define generic classes and methods.

public class Main {
    // Generic method to swap values
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static void main(String[] args) {
        Integer[] numbers = {5, 10};
        swap(numbers, 0, 1);
        System.out.println("numbers[0]: " + numbers[0] + ", numbers[1]: " + numbers[1]);  // Output: numbers[0]: 10, numbers[1]: 5

        String[] words = {"Hello", "World"};
        swap(words, 0, 1);
        System.out.println("words[0]: " + words[0] + ", words[1]: " + words[1]);  // Output: words[0]: World, words[1]: Hello
    }
}
Copied!

In C#, generics are used similarly to Java.

using System;

class Program
{
    // Generic method to swap values
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

    static void Main()
    {
        int x = 5, y = 10;
        Swap(ref x, ref y);
        Console.WriteLine($"x: {x}, y: {y}");  // Output: x: 10, y: 5

        string s1 = "Hello", s2 = "World";
        Swap(ref s1, ref s2);
        Console.WriteLine($"s1: {s1}, s2: {s2}");  // Output: s1: World, s2: Hello
    }
}
Copied!

In TypeScript, generics are used to define generic functions and classes.

// Generic function to swap values
function swap<T>(a: T, b: T): [T, T] {
    return [b, a];
}

let x = 5, y = 10;
[x, y] = swap(x, y);
console.log(`x: ${x}, y: ${y}`);  // Output: x: 10, y: 5

let s1 = "Hello", s2 = "World";
[s1, s2] = swap(s1, s2);
console.log(`s1: ${s1}, s2: ${s2}`);  // Output: s1: World, s2: Hola
Copied!

Rust uses generics extensively along with Traits (its version of interfaces). Types like Option<T> or Result<T, E> are the basis of error handling, avoiding traditional exceptions.