Узагальнення в функціях. Шаблонні функції (template functions). Ключові слова template, class, typename. Явне “перевантаження” шаблонної функції

Узагальнення в функціях. Шаблонні функції (template functions). Ключові слова template, class, typename. Явне “перевантаження” шаблонної функції


Зміст



1. Узагальнення у функціях. Поняття шаблонної функції. Загальна форма оголошення шаблонної функції. Ключові слова template, class, typename

Поняття шаблону у функції має на увазі використання узагальненого типу даних в якості вхідних та вихідних параметрів функції. Замість конкретного типу даних у своїй роботі функція використовує деякий узагальнений тип, який має узагальнене ім’я, наприклад T.

Загальна форма узагальненої функції має вигляд:

template <class T> return_type FunName(list_of_parameters)
{
    // ...
}

де

  • return_type – тип, що повертає функція;
  • Fun_Name – ім’я шаблонної функції;
  • list_of_parameters – параметри шаблонної функції, що мають узагальнений тип T.

При оголошенні шаблонної функції, замість ключового слова class можна застосовувати ключове слово typename. У цьому випадку загальна форма узагальненої функції матиме вигляд:

template <typename T> return_type FunName(list_of_parameters)
{
    // ...
}

Наприклад. Оголошення функції, що отримує вхідним параметром змінну узагальненого типу T та повертає значення узагальненого типу T має вигляд:

template <class T>
T FunName(T parameterName)
{
    // тіло функції
    // ...
}

де

  • FunName – ім’я узагальненої функції;
  • T – ім’я узагальненого типу. При оголошенні шаблонної функції, це ім’я може бути будь-яким, наприклад TYPE;
  •  parameterName – ім’я параметру узагальненого типу T, який використовується у функції FunName.

2. Приклади узагальнених (шаблонних) функцій

Приклад 1. У прикладі реалізовано шаблонну функцію Add(), що додає два параметри узагальненого типу TT. У функції main() продемонстровано використання функції Add()

#include "stdafx.h"
#include <iostream>
using namespace std;

// Шаблонна функція, що повертає суму двох величин типу TT
template <class TT>
TT Add(TT t1, TT t2)
{
    return t1 + t2;
}

int main()
{
    // Використання шаблонної функції Add
    // 1. Для типу double
    double d1 = 3.85, d2 = 2.50;
    double d3;

    d3 = Add(d1, d2); // d3 = 6.35

    // 2. Для типу int
    int i1 = 25;
    int i2 = 13;
    int i3;
    i3 = Add(i1, i2); // i3 = 38

    // 3. Для типу string
    string s1 = "abc";
    string s2 = "def";
    string s3 = Add(s1, s2); // s3 = "abcdef"

    cout << s3.c_str() << endl;

    return 0;
}

Як видно з прикладу, шаблонна функція

template <class TT>
TT Add(TT t1, TT t2)
{
    return t1 + t2;
}

використовується для додавання параметрів типів double, int, string. У цій функції узагальнений тип має ім’я TT.

Приклад 2. Реалізовано шаблонну функцію Equal(), яка порівнює два вхідні параметри узагальненого типу X. Функція повертає true, якщо значення параметрів однакові. В іншому випадку функція повертає false.

#include "stdafx.h"
#include <iostream>
using namespace std;

// функція, яка порівнює два вхідні параметри типу X
template <typename X>
bool Equal(X a, X b)
{
    return a==b;
}

int main()
{
    // демонстрація шаблонної функції Equal()
    bool res;

    res = Equal(5, 6); // res = 0
    res = Equal("abcd", "abcd"); // res = 1
    res = Equal(5.5, 5.5); // res = 1
    res = Equal('A', 'B'); // res = 0

    return 0;
}

Як видно з прикладу, виклик функції Equal() здійснюється для різних типів: int, string, double, char.

3. Як працює компілятор у випадку використання шаблонної функції

Якщо компілятор зустрічає шаблонну функцію, він генерує версії функції для кожного варіанту її застосування з конкретним типом. Тобто, якщо шаблонна функція викликається для типів double, int, string, то компілятор генерує 3 версії функції для кожного типу.

Наприклад. Нехай задано функцію, яка додає два параметри узагальненого типу T:

T Add(T t1, T t2)
{
    return t1 + t2;
}

При наступному виклику такої функції

int a = Add(3, 8);
double d = Add(3.8, 2.9);
string s = Add("Hello ", "world!");

компілятор згенерує такі реалізації функції Add():

int Add(int t1, int t2)
{
    return t1 + t2;
}

double Add(double t1, double t2)
{
    return t1 + t2;
}

string Add(string t1, string t2)
{
    return t1 + t2;
}

Висновок: компілятор генерує стільки варіантів функції, скільки існує способів її викликів у програмі.

4. Скільки узагальнених типів даних можна визначити для шаблонної функції?

Якщо потрібно, в шаблонній функції може бути визначено будь-яку кількість узагальнених типів (два, три і т.д.). У цьому випадку узагальнені типи перераховуються через кому з допомогою ключового слова class або typename.

Наприклад. Для двох типів з іменами T1, T2 шаблонна функція матиме такий загальний вигляд

template <class T1, class T2>
return_type FunName(list_of_parameters)
{
    // ...
}

де

  • return_type – тип, що повертає функція;
  • Fun_Name – ім’я шаблонної функції;
  • list_of_parameters – параметри шаблонної функції, що оперує узагальненими типами T1, T2.

5. Приклад шаблонної функції, що містить два узагальнені типи

Нижче наведено приклад шаблонної функції, яка використовує два узагальнені типи T1, T2. Функція отримує два параметри типів T1, T2 і виводить значення цих параметрів на екран. Для оголошення типів використовується ключове слово typename. Можна також використовувати ключове слово class.

#include "stdafx.h"
#include <iostream>
using namespace std;

// Шаблонна функція, що виводить значення величин t1, t2
template <typename T1, typename T2>
void Display(T1 t1, T2 t2)
{
    cout << "t1 = " << t1 << "; t2 = " << t2 << endl;
}

int main()
{
    int a = 30;
    double d = 9.83;
    char c = 'X';
    bool b = true;

    Display(a, d); // t1 = 30; t2 = 9.83
    Display(b, c); // t1 = 1; t2 = X
    Display(8, -100); // t1 = 8; t2 = -100

    return 0;
}

6. Що таке явне “перевантаження” шаблонної функції. Яким чином здійснюється явне “перевантаження” шаблонної функції? Приклад

Явне “перевантаження” (explicit specialization) шаблонної функції – це оголошення ще однієї функції з таким самим іменем, але вже для конкретного типу (наприклад, int, double). Таким чином, “перевантажена” функція заміщає шаблонну (узагальнену) функцію для деякого конкретного випадку (типу даних). Для інших випадків викликається узагальнений варіант шаблонної функції.

Приклад. У прикладі шаблонна функція Display() “перевантажена” для типу string. Таке “перевантаження” є логічним, оскільки в операторі cout вивести тип string звичайним рядком

cout << "t = " << t << endl;

не вдасться. У цьому випадку вийде помилка компіляції, оскільки для типу string потрібно перетворення t.c_str() у тип const char *. Таке перетворення реалізоване у “перевантаженій” функції Display(), що отримує параметром змінну t типу string.

#include "stdafx.h"
#include <iostream>
using namespace std;

// шаблонна функція для типу T
template <class T>
void Display(T t)
{
    cout << "t = " << t << endl;
}

// явно "перевантажена" функція Display() для типу string
void Display(string t)
{
    // вивід типу string виділений окремою функцією
    cout << "t = " << t.c_str() << endl;
}

int main()
{
    int a = 10;
    double d = 3.55;
    string s = "Hello";

    // виклик шаблонної функції
    Display(a); // t = 10
    Display(d); // t = 3.55

    // виклик перевантаженої функції для типу string
    Display("abcd"); // t = abcd
    Display(s); // t = Hello

    return 0;
}

7. Що таке перевантаження шаблонної функції? Приклад

“Перевантаження” шаблонної функції не потрібно плутати з явним “перевантаженням” шаблонної функції (див. п. 6).

“Перевантаження” шаблонної функції – це використання того самого імені для шаблонної функції, але з іншою кількістю параметрів.

Приклад. Нижче наведено “перевантаження” шаблонної функції Max(), що знаходить максимальне значення між двома, трьома та чотирма величинами узагальненого типу T.

#include "stdafx.h"
#include <iostream>
using namespace std;

// шаблонна функція Max() для типу T,
// яка повертає максимум між двома значеннями value1, value2
template <typename T>
T Max(T value1, T value2)
{
    if (value1 > value2) return value1;
    return value2;
}

// "перевантажена" шаблонна функція Max()
// знаходить максимум між трьома значеннями value1, value2, value3
template <class T>
T Max(T value1, T value2, T value3)
{
    T max = value1;
    if (max < value2) max = value2;
    if (max < value3) max = value3;
    return max;
}

// "перевантажена" шаблонна функція Max()
// максимум між чотирма значеннями
template <typename T>
T Max(T value1, T value2, T value3, T value4)
{
    T max = value1;
    if (max < value2) max = value2;
    if (max < value3) max = value3;
    if (max < value4) max = value4;
    return max;
}

int main()
{
    // демонстрація використання "перевантаженої" шаблонної функції Max()
    int i;
    double d;
    bool b;
    char c;

    // для типу int
    i = Max(8, 11); // i = 11
    i = Max(9, 13, 14); // i = 14
    i = Max(8, 10, 4, -3); // i = 10

    // для типу double
    d = Max(3.5, 1.2); // d = 3.5
    d = Max(1.3, -9.20, 100.33); // d = 100.33
    d = Max(-39.5, 8.82, 1.23); // d = 8.82

    // для типу bool
    b = Max(true, false); // b = 1

    // для типу char
    c = Max('C', 'F'); // c = 'F'
    c = Max('F', 'H', 'G'); // c = 'H'
    c = Max('M', 'A', 'X', 'W'); // c = 'X'

    return 0;
}

8. Яким чином можна застосовувати стандартні та узагальнені параметри в шаблонній функції? Приклад

У шаблонній функції дозволяється використовувати стандартні параметри. Ці параметри оголошуються так само, як і для звичайної нешаблонної функції.

Приклад. Реалізується функція Mult(), яка додає задану кількість разів значення узагальненого параметру типу T. Кількість додавань задається цілим числом count. Функція повертає результат, що є сумою параметрів типу T.

У прикладі демонструється використання функції для типів int та double. Для інших типів потрібно визначати “перевантажені” реалізації функції Mult().

#include "stdafx.h"
#include <iostream>
using namespace std;

// функція, яка додає значення value задану кількість разів
// функція отримує вхідним параметром цілочисельне значення count
template <class T>
T Mult(T value, int count)
{
    T res = value;
    for (int i = 1; i < count; i++)
    {
        res = res + value;
    }
    return res;
}

int main()
{
    // демонстрація шаблонної функції Mult()
    int i;
    double d;
    string s;

    // для типу int
    i = Mult(5, 2); // i = 10
    i = Mult(6, 4); // i = 24

    // для типу double
    d = Mult(3.5, 3); // d = 10.5
    d = Mult(6.2, 8); // d = 49.6

    return 0;
}

9. Приклад шаблонної функції, що реалізує сортування методом “бульбашки”

У прикладі реалізована шаблонна функція Sort(), що сортує масив елементів деякого узагальненого типу T.  Функція отримує параметром посилання на масив та кількість елементів масиву. У функції main() демонструється застосування шаблонної функції Sort().

#include "stdafx.h"
#include <iostream>
using namespace std;

// шаблонна функція, яка сортує масив чисел у порядку спадання
template <class T>
void Sort(T A[], int count)
{
    int i, j;
    T tmp;
    for (i=0; i<count-1; i++)
        for (j=i; j>=0; j--)
            if (A[j] < A[j + 1])
            {
                // обміняти значеннями A[i] та A[i+1]
                tmp = A[j];
                A[j] = A[j + 1];
                A[j + 1] = tmp;
            }
}

int main()
{
    // демонстрація шаблонної функції Sort()
    int M[] = { 3, 5, -1, 2, -9, 7, 8, 3 };
    double D[] = { 3.3, 2.8, 1.5, 4.8, 6.1, 1.3 };

    // для типу int
    Sort(M, 8); // M = { }

    for (int i = 0; i < 8; i++)
        cout << M[i] << ", ";

    cout << endl << endl;

    // для типу double
    Sort(D, 6);
    for (int i = 0; i < 6; i++)
        cout << D[i] << ", ";

    return 0;
}


Зв’язані теми