Language: EN

cpp-como-usar-templates

What are templates and how to use them in C++

Templating in C++ is a mechanism that allows writing generic code, meaning it can work with different types of data without having to rewrite it for each one.

Instead of defining a specific function or class for a data type (for example, int, double, string, etc.), we define a generic version that works for different types.

It’s called that because it’s like making a “template” for functions or classes.

Templating does not imply a loss of efficiency, as the templating work occurs during compilation, not during execution.

The compiler generates specific code for each type you use with the template (this process is called template instantiation), generating customized versions of the code for the types needed.

Function Templates

Function templates allow defining generic functions that can work with different types of data.

The basic syntax of a function template is:

template <typename T>
T genericFunction(T a, T b) {
    // Function code
}

Instead of T, you could use any other name, but this name is often used.

Let’s see it with an example.

#include <iostream>

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

int main() {
    std::cout << "Add integers: " << add(3, 4) << std::endl;
    std::cout << "Add floats: " << add(3.5, 4.5) << std::endl;
    return 0;
}

In this example, the function add can work with both integers and floats because we have defined it as a template.

Internally, the compiler generates a function for int and another for double, ensuring that each is as efficient as if you had written them manually.

Class Templates

In addition to functions, it is also possible to use class templates, which allow us to define generic classes that can handle different types of data.

The basic syntax of a class template is very similar:

template <typename T>
class GenericClass {
private:
    T data;
public:
    GenericClass(T data) : data(data) {}
    T getData() { return data; }
};

Let’s see it with an example:

#include <iostream>

template <typename T>
class Box {
private:
    T content;
public:
    Box(T content) : content(content) {}
    T getContent() { return content; }
};

int main() {
    Box<int> intBox(123);
    Box<std::string> stringBox("Hello, C++");

    std::cout << "Content of intBox: " << intBox.getContent() << std::endl;
    std::cout << "Content of stringBox: " << stringBox.getContent() << std::endl;

    return 0;
}

In this example, the class Box can contain both integers and strings, or anything else (as a good box should).

Multi-parameter Template

It is also possible to define templates that accept more than one type parameter.

template <typename T, typename U>
class Pair {
private:
    T first;
    U second;
public:
    Pair(T first, U second) : first(first), second(second) {}
    T getFirst() { return first; }
    U getSecond() { return second; }
};

int main() {
    Pair<int, std::string> pair(1, "One");
    std::cout << "First: " << pair.getFirst() << ", Second: " << pair.getSecond() << std::endl;
    return 0;
}

Template Specialization

Template specialization allows defining specific implementations for certain data types.

#include <iostream>

template <typename T>
class Printer {
public:
    void print(T data) {
        std::cout << "Data: " << data << std::endl;
    }
};

// Specialization for char*
template <>
class Printer<char*> {
public:
    void print(char* data) {
        std::cout << "String: " << data << std::endl;
    }
};

int main() {
    Printer<int> intPrinter;
    intPrinter.print(42);

    Printer<char*> stringPrinter;
    stringPrinter.print("Hello, specialization!");

    return 0;
}

In this example, the Printer class is specialized for the char* type, allowing a specific implementation for strings.

Template Type Deduction

Template type deduction is the process by which the C++ compiler automatically determines the type of the template parameters based on the arguments we pass.

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

int main() {
    int x = 5, y = 10;
    double p = 2.5, q = 3.5;

    // The compiler automatically deduces the type of T
    std::cout << sum(x, y) << std::endl;   // Deduced: T = int
    std::cout << sum(p, q) << std::endl;   // Deduced: T = double
}

In this example:

  • When you call sum(x, y), the compiler sees that x and y are of type int, so it deduces that T = int.
  • When you call sum(p, q), it deduces that T = double.

This eliminates the need to explicitly write sum<int> or sum<double>.

Deduction Guides

Since C++17, we can also “help” the compiler deduce types for class templates using deduction guides.

template <typename T>
class Box {
public:
    T content;
    Box(T c) : content(c) {}
};

// Deduction guide
Box(const char*) -> Box<std::string>;

int main() {
    Box c1(42);          // Deduced: T = int
    Box c2("Hello");     // Deduced: T = std::string (thanks to the deduction guide)
}

This is useful when the constructor of a class does not always clearly indicate the type of the template.