Sari la conținut

Șabloane de funcție și deducere de tip

Aflați cum să scrieți funcții care funcționează cu orice tip. Înțelegeți deducerea argumentelor șablonului, parametrii non-tip și de ce șabloanele se află în anteturi.

De ce șabloane de funcție?

Fără șabloane, scrierea unei funcții care funcționează pentru mai multe tipuri înseamnă duplicarea logicii. Veți scrieint max(int, int),double max(double, double),std::string max(std::string, std::string)— logică identică repetată pentru fiecare tip. Șabloanele de funcție vă permit să scrieți algoritmul o dată și să permită compilatorului să genereze automat versiuni specifice tipului.

Un șablon de funcție nu este o funcție – este un plan din care compilatorul instanțiază funcțiile reale atunci când le utilizați cu anumite tipuri. Acest lucru se întâmplă în întregime în timpul compilării, producând cod identic cu ceea ce ați scrie manual - zero overhead.

Sintaxa șablonului

Un șablon de funcție este introdus cu cuvântul cheie template urmat de o listă de parametri între paranteze unghiulare. Puteți folosi typename sau class în mod interschimbabil pentru parametrii de tip - ei înseamnă exact același lucru. Convenția modernă preferătypename.

function_template.cpp
#include <iostream>
#include <string>

// 'typename' and 'class' are interchangeable here
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Equivalent — 'class' means the same thing
template <class T>
T minimum(T a, T b) {
    return (a < b) ? a : b;
}

int main() {
    std::cout << maximum(3, 7)          << '\n'; // deduces T = int
    std::cout << maximum(3.14, 2.71)    << '\n'; // deduces T = double
    std::cout << maximum<std::string>("hello", "world") << '\n';
    // explicit instantiation needed — "hello" is const char*, not std::string
}

Reguli de deducere a argumentului șablon

Compilatorul deduce argumentele șablon prin potrivirea tipurilor de argumente ale funcției cu modelele de parametri. Regulile cheie sunt:

1. Se preferă potrivirea exactă —maximum(3, 7)deduceT = intdeoarece ambele argumente suntint.
2. Fără conversii implicite în timpul deducerii —maximum(3, 7.0)este o eroare: compilatorul vedeT = intdin primul argument șiT = doubledin al doilea și refuză să aleagă unul.
3. Decaiare de la matrice la pointer — cândTapare ca parametru de valoare, matricele decad la pointeri. UtilizațiT(&)[N]pentru a captura tipul și dimensiunea matricei.
4. Const/volatil de nivel superior este eliminat din parametrii de valoare, dar păstrat pentru parametrii de referință/pointer.
5. Reducerea referințelor — dacăTeste dedus ca tip de referință (în referințele de redirecționare), se aplică regulile de restrângere a referințelor:T& &devineT&,T& &&devineT&,T&& &devineT&,T&& &&devineT&&.

Când deducerea eșuează sau este ambiguă, trebuie să furnizați argumente șablon explicit:maximum(3, 7.0).

Parametri de șablon non-tip

Parametrii șablonului nu se limitează la tipuri. Parametrii șablonului fără tip (NTTP) acceptă valori în timp de compilare, cum ar fi numere întregi, pointeri sau (de la C++20) tipuri în virgulă mobilă și clase. Sunt puternice pentru încorporarea constantelor de timp de compilare în sistemul de tipuri.

non_type_params.cpp
#include <iostream>
#include <array>
#include <cstddef>

// Non-type parameter: N is a compile-time size_t value
template <typename T, std::size_t N>
T sum(const std::array<T, N>& arr) {
    T total{};
    for (const auto& elem : arr) {
        total += elem;
    }
    return total;
}

// C++20: auto non-type parameter accepts any structural type
template <auto Value>
constexpr auto doubled = Value * 2;

int main() {
    std::array<int, 4> nums = {1, 2, 3, 4};
    std::cout << sum(nums) << '\n';     // 10, deduces T=int, N=4

    std::cout << doubled<21> << '\n';   // 42
    std::cout << doubled<3.14> << '\n'; // 6.28 (C++20)
}
Notă

Fiecare combinație unică de argumente șablon determină compilatorul să genereze o instanțiere separată - o copie distinctă a funcției din binarul tău. Apelareamaximum,maximumșimaximumproduce trei funcții. Acest lucru este de obicei bine, dar în bazele de cod mari șabloanele pot contribui la cod bloat (binare mai mari, timpi mai mari de compilare). Strategiile de atenuare includ: utilizarea declarațiilor șabloane externe pentru a controla unde au loc instanțele, factorizarea codului independent de tip din șabloane și utilizarea ștergerii tipului atunci când polimorfismul de rulare este acceptabil.

Capcană

Deoarece compilatorul trebuie să vadă definiția completă a șablonului pentru a-l instanția, șabloanele nu pot fi împărțite în fișiere.h/.cppașa cum pot funcțiile obișnuite. Dacă puneți o definiție de șablon într-un fișier.cppși încercați să o utilizați dintr-o altă unitate de traducere, veți obține o eroare de linker (referință nedefinită). Soluția standard este să puneți întreaga definiție a șablonului în fișierul antet. Dacă timpii de compilare devin o problemă, puteți folosi instanțierea explicită: puneți definiția într-un fișier.cppși adăugațitemplate int maximum(int, int);pentru a instanția explicit specializările de care aveți nevoie.

Instanțierea explicită

Puteți controla în mod explicit unde și când sunt instanțiate șabloanele. Acest lucru poate reduce timpii de compilare în proiecte mari prin prevenirea instanțiilor redundante între unitățile de traducere.

explicit_instantiation.h
// ---- maximum.h ----
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Suppress implicit instantiation in every TU that includes this header
extern template int maximum<int>(int, int);
extern template double maximum<double>(double, double);

// ---- maximum.cpp ----
#include "maximum.h"

// Explicit instantiation: generate code here, once
template int maximum<int>(int, int);
template double maximum<double>(double, double);
Idei cheie
  • Șabloanele de funcție sunt planuri — compilatorul generează funcții concrete pentru fiecare set de argumente șablon
  • typenameșiclasssunt interschimbabile în listele de parametri șablon
  • Deducerea argumentului șablon nu realizează conversii implicite — deducțiile ambigue sunt erori
  • Parametrii șablonului care nu sunt de tip încorporează valori de compilare în sistemul de tip
  • Șabloanele trebuie să fie definite în anteturi (sau să folosească instanțierea explicită), deoarece compilatorul are nevoie de definiția completă pentru a instanția

Test — Verifică-ți cunoștințele

(15 XP)

1. Ce se întâmplă când apelați`maximum(3, 7.0)`cu un șablon`template<typename T> T maximum(T a, T b)`?

2. De ce trebuie de obicei plasate definițiile șablonului în fișierele antet?

3. What is the purpose of`extern template int maximum<int>(int, int);`?