MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Șabloane de funcții

Șabloanele de funcții permit definirea unor funcții care nu depind de un anumit tip concret.

Să începem cu un exemplu în care pot fi utile. De exemplu, trebuie să definim o funcție care adună două numere de tip int, double și std::string. Prima soluție care vine în minte este supraîncărcarea funcției — să definim o versiune pentru fiecare tip:

#include <iostream>

int add(int, int);
double add(double, double);
std::string add(std::string, std::string);

int main()
{
    std::cout << "int: " << add(4, 5) << std::endl; 
    std::cout << "double: " << add(4.4, 5.5) << std::endl;
    std::cout << "string: " << add(std::string("hel"), std::string("lo")) << std::endl;
}

int add(int x, int y)
{
    return x + y;
}
double add(double x, double y)
{
    return x + y;
}

std::string add(std::string str1, std::string str2)
{
    return str1 + str2;
}

Acest exemplu funcționează perfect și face calculele așteptate. Totuși, observăm că funcția add se repetă practic, doar tipurile parametrilor și ale valorii returnate sunt diferite.

Acum să folosim șabloane de funcții. Acestea oferă un model după care pot fi create funcții concrete, specifice unor anumite tipuri:

#include <iostream>

template<typename T> T add(T, T);

int main()
{
    std::cout << "int: " << add(4, 5) << std::endl; 
    std::cout << "double: " << add(4.4, 5.5) << std::endl;
    std::cout << "string: " << add(std::string("hel"), std::string("lo")) << std::endl;
}
template<typename T> T add(T a, T b)
{
    return a + b;
}

Definirea șablonului începe cu cuvântul template, urmat de typename și lista parametrilor de șablon:

template<typename T> T add(T a, T b)

Aici, T este un identificator arbitrar, de obicei o literă mare, care va reprezenta un tip ce va fi determinat la compilare. Acesta poate fi int, double, std::string sau orice alt tip care suportă operația de adunare.

La apelarea funcției add, compilatorul determină tipul pe baza parametrilor și creează o instanță specifică pentru acel tip. Dacă funcția este apelată din nou cu același tip, se reutilizează instanța existentă.

Putem de asemenea folosi referințe constante:

#include <iostream> 

template<typename T> T add(const T&, const T&); 

int main()
{
    std::cout << "int: " << add(4, 5) << std::endl; 
    std::cout << "double: " << add(4.4, 5.5) << std::endl;
    std::cout << "string: " << add(std::string("hel"), std::string("lo")) << std::endl;
}
template<typename T> T add(const T& a, const T& b)
{
    return a + b;
}

Alt exemplu — o funcție care schimbă valorile:

#include <iostream> 

template<typename T> void swap(T&, T&); 

int main()
{
    int c {30};
    int d {10};
    swap(c, d);
    std::cout << "c = " << c << "\t d = " << d << std::endl;    // с = 10   d = 30
}

template <typename T> void swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

Funcția swap primește doi parametri de orice tip și le inversează valorile.

Exemplu cu pointeri — funcție care determină valoarea maximă:

#include <iostream> 

template<typename T> T* max(T*, T*); 

int main()
{
    int a{4}, b{5};
    std::cout << "int: " << *max(&a, &b) << std::endl; // int: 5

    double c{3.4}, d{2.3};
    std::cout << "double: " << *max(&c, &d) << std::endl;   // double: 3.4
}
template<typename T> T* max(T* a, T* b)
{
    return *a > *b ? a : b;
}

Apel explicit pentru un anumit tip

Până acum, compilatorul deducea automat tipul. Dar putem specifica tipul în mod explicit:

#include <iostream> 

template<typename T> T add(const T&, const T&); 

int main()
{
    double d { add<double>(3.3, 2.2)};
    std::cout << "d: " << d << std::endl;   // d: 5.5
    d  = add<double>(3, 2);
    std::cout << "d: " << d << std::endl;   // d: 5
}
template<typename T> T add(const T& a, const T& b)
{
    return a + b;
}

Aici specificăm că vrem să folosim tipul double:

double d { add<double>(3.3, 2.2)};

Chiar și în cazul în care transmitem literali întregi:

d  = add<double>(3, 2);

Funcția va fi instanțiată pentru double.

Supraîncărcarea funcțiilor și parametrii șablon

Pe de o parte, utilizarea șabloanelor permite reducerea nevoii de a supraîncărca funcții, deoarece putem abstractiza față de tipurile concrete. Pe de altă parte, există situații când sunt necesare versiuni diferite ale funcției. În astfel de cazuri, putem combina supraîncărcarea funcțiilor cu șabloanele. De exemplu, să găsim valoarea maximă dintre două elemente sau dintr-un tablou:

#include <iostream>

template<typename T> const T* max(const T*, const T*);
template <typename T> const T* max(const T[], unsigned);

int main()
{
    int a{4}, b{5};
    std::cout << *max(&a, &b) << std::endl;

    double numbers[]{3.4, 2.3, 6.1, 4.3};
    std::cout << *max(numbers, std::size(numbers)) << std::endl;
}

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

template <typename T>
const T* max(const T data[], unsigned size)
{
    const T* result{};
    for (unsigned i{}; i < size; i++)
    {
        if (!result || data[i] > *result)
            result = &data[i];
    }
    return result;
}

Utilizarea mai multor parametri de tip

Putem folosi mai mulți parametri. De exemplu, să definim o funcție de transfer de la un cont la altul cu un cod de operație:

#include <iostream>

template <typename T, typename K>
void transfer(T, T, K, int);

int main()
{
    transfer("id1234", "id5678", 2804, 5000);
}

template <typename T, typename K>
void transfer(T fromAccount, T toAccount, K code, int sum)
{
    std::cout << "From: " << fromAccount << "\nTo: " << toAccount
              << "\nSum: " << sum << "\nCode: " << code << std::endl;
}

Deducerea tipului și decltype(auto)

Când nu cunoaștem exact tipul valorii returnate sau dorim ca compilatorul să îl deducă, putem folosi decltype(auto):

#include <iostream>

template <typename T> decltype(auto) average(const T (&data)[], unsigned size)
{
    T result{};
    for (unsigned i{}; i < size; i++)
    {
        result += data[i];
    }
    return result / size;
}

int main()
{
    int numbers[]{1, 3, 4, 5, 6};
    std::cout << average(numbers, std::size(numbers)) << std::endl;
}

Parametri netipizați

C++ permite definirea de șabloane cu parametri netipizați (concreți), de exemplu:

#include <iostream>

template <typename T, unsigned N=1> void print(const T&);

int main()
{
    print<int, 4>(3);
}

template <typename T, unsigned N> void print(const T& value)
{
    for (unsigned i{}; i < N; i++)
    {
        std::cout << value << std::endl;
    }
}

Alt exemplu — calculul lungimii unui tablou:

#include <iostream>

template <typename T, size_t N> size_t size(const T (&data)[N]) { return N; }

int main()
{
    const int numbers1[]{1, 2, 3, 4, 5};
    const double numbers2[]{1.2, 2.3, 3.4};
    const char* people[] {"Sam", "Tom", "Bob", "Mike"};

    std::cout << "numbers1 size: " << size(numbers1) << std::endl;
    std::cout << "numbers2 size: " << size(numbers2) << std::endl;
    std::cout << "people size: " << size(people) << std::endl;
}

Calculul mediei fără a transmite dimensiunea tabloului:

#include <iostream>

template <typename T, size_t N> T average(const T (&)[N]);

int main()
{
    int numbers1[]{1, 2, 3, 4, 5};
    std::cout << average(numbers1) << std::endl;

    double numbers2[]{1.1, 3.2, 4.3, 5.4, 6.5, 2.6};
    std::cout << average(numbers2) << std::endl;
}

template <typename T, size_t N> T average(const T (&data)[N])
{
    T result{};
    for (unsigned i{}; i < N; i++)
    {
        result += data[i];
    }
    return result / N;
}

Aici, este definit un parametru N de tip size_t pentru a reprezenta dimensiunea matricei. Când se apelează o funcție pe baza unui array transmis funcției, acest parametru primește o valoare specifică - dimensiunea array-ului.

Auto pentru deducerea tipurilor începând cu C++20

Este demn de remarcat faptul că, începând cu standardul C++20, este posibil să se definească parametri ale căror tipuri sunt deduse automat pe baza argumentelor transmise. În mod similar, puteți afișa și tipul de rezultat. Cuvântul cheie auto este folosit pentru aceasta. În mod similar, puteți utiliza expresiile auto*, auto& și const auto& pentru a defini tipurile de parametri și rezultate ale funcțiilor:

#include <iostream>

auto add(const auto& a, const auto& b)
{
    return a + b;
}

int main()
{
    const int n1{3};
    const int n2{4};
    std::cout << add(n1, n2) << std::endl;

    const double d1{3.3};
    const double d2{4.4};
    std::cout << add(d1, d2) << std::endl;
}

Această funcție add este universală și poate adăuga orice tipuri care suportă operatorul +.