Обобщения в функциях. Шаблонные функции (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"

    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'

    cout << c << endl;

    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[j] и A[j+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;
}


Связанные темы