template în C++

Scrie o funcție sau o clasă o singură dată, pentru orice tip; compilatorul generează varianta potrivită pentru fiecare tip folosit.

Sintaxă
template <typename T>
T maxim(T a, T b) { ... }

template <typename T, int N>
class Tablou { ... };

template <typename... Args>   // C++11, număr variabil de tipuri
void f(Args... args);

Ce face template

Un șablon (template) este un „tipar” de cod cu tipuri ca parametri. template <typename T> T maxim(T a, T b) funcționează pentru int, double, std::string și orice tip care se poate compara. La apelul maxim(3, 7), compilatorul deduce T = int și generează funcția pentru int.

Toată biblioteca standard este făcută din șabloane: std::vector<int>, std::map<std::string, double>. Parametrii pot fi și valori constante (template <int N>), iar din C++11 pot fi în număr variabil (typename... Args).

Pentru că sunt generate la compilare, șabloanele se scriu de obicei complet în fișiere antet. Din C++20, cerințele asupra tipurilor se pot scrie clar cu concepte.

Exemple

Exemplul 1: Funcție șablon

Aceeași funcție merge pentru trei tipuri. La ultimul apel tipul este dat explicit, <double>, pentru că argumentele au tipuri diferite.

#include <print>
#include <string>

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

int main() {
    std::println("{}", maxim(3, 7));
    std::println("{}", maxim(2.5, 1.5));
    std::println("{}", maxim(std::string("Ana"), std::string("Bogdan")));
    std::println("{}", maxim<double>(4, 4.5));
}

Rezultat

7
2.5
Bogdan
4.5

Exemplul 2: Clasă șablon

#include <print>
#include <string>

template <typename T>
class Pereche {
public:
    Pereche(T a, T b) : primul_(a), al_doilea_(b) {}
    T mai_mic() const { return primul_ < al_doilea_ ? primul_ : al_doilea_; }
private:
    T primul_, al_doilea_;
};

int main() {
    Pereche<int> note(9, 7);
    Pereche<std::string> orase("Iași", "Arad");
    std::println("Nota mai mică: {}", note.mai_mic());
    std::println("Primul alfabetic: {}", orase.mai_mic());
}

Rezultat

Nota mai mică: 7
Primul alfabetic: Arad

Exemplul 3: Parametru valoare și număr variabil de argumente

N este o constantă cunoscută la compilare. suma(...) folosește o „expresie fold” (C++17) ca să adune toate argumentele.

#include <array>
#include <print>

template <int N>
std::array<int, N> primele_patrate() {
    std::array<int, N> t{};
    for (int i = 0; i < N; ++i) t[i] = (i + 1) * (i + 1);
    return t;
}

template <typename... Numere>
auto suma(Numere... n) {
    return (n + ... + 0);
}

int main() {
    std::println("{}", primele_patrate<5>());
    std::println("Suma: {}", suma(10, 20, 30, 40));
}

Rezultat

[1, 4, 9, 16, 25]
Suma: 100

Greșeli frecvente

  • Declari șablonul într-un fișier .h și îl definești într-un .cpp. La legare primești „undefined symbol”, pentru că compilatorul nu vede definiția când generează varianta pentru tipul tău. Pune definiția în antet.
  • Apelezi maxim(4, 4.5): T ar trebui să fie și int și double, deci deducerea eșuează. Dă tipul explicit (maxim<double>) sau folosește doi parametri de tip.
  • Folosești un tip care nu are operațiile cerute de șablon (de exemplu maxim pe o structură fără >). Eroarea poate fi foarte lungă; conceptele (C++20) o fac scurtă și clară.

Comenzi înrudite

  • typename: Introduce un parametru de tip într-un șablon sau anunță compilatorul că un nume care depinde de șablon este un tip.
  • concept: Dă un nume unui set de cerințe pentru tipuri, folosit ca să restrângi ce tipuri acceptă un șablon.
  • requires: Scrie cerințe pentru parametrii unui șablon: ca clauză care restrânge șablonul sau ca expresie care verifică dacă un cod este valid.
  • if constexpr: Un if evaluat la compilare: ramura falsă este eliminată complet, deci poate conține cod care nu s-ar compila pentru tipul curent.
  • auto: Lasă compilatorul să deducă tipul variabilei din valoarea cu care este inițializată.
  • std::vector: Tabloul dinamic din C++: ține elementele unul după altul în memorie și își mărește singur dimensiunea.