Шаблонные (обобщенные) функции

Если присмотреться к реализации функции sum(), то можно заметить, что вне зависимости от типа параметров внутри функции будет одна и та же инструкция return x + y;, так как оператор + позволяет сложить числа любого типа. Для таких случаев в языке C++ вместо перегрузки функции следует применять шаблонные функции, которые называют также обобщенными. Компилятор на основе шаблонной функции автоматически создаст перегруженные версии функции в зависимости от имеющихся способов ее вызова в программе. Описывается шаблонная функция по следующей схеме:

template<typename Тип1[, ..., typename ТипN]>
Тип Название_функции(Тип Название_переменной1
                    [, ..., Тип Название_переменнойN])
{
    Тело_функции
   [return[ Возвращаемое_значение];]
}

После ключевого слова template внутри угловых скобок через запятую указываются обобщенные названия типов или реальные. Обобщенные названия используются для описания типов параметров и могут использоваться внутри функции. При компиляции обобщенные типы будут заменены реальными типами данных. Перед названием обобщенного типа могут быть указаны ключевые слова typename или class, которые обозначают одно и то же. Остальное описание шаблонной функции совпадает с описанием обычной функции, только вместо реальных типов данных указываются названия обобщенных типов, перечисленных после ключевого слова template.

При вызове шаблонной функции после названия функции внутри угловых скобок можно указать реальные типы данных через запятую:

Название_функции<Тип1, ..., ТипN>(Параметры)

В большинстве случаев компилятор может самостоятельно определить реальные типы данных по типу передаваемых параметров, поэтому шаблонную функцию можно вызвать как обычную функцию:

Название_функции(Параметры)

В качестве примера создадим шаблонную функцию для сложения двух чисел любых одинаковых типов (листинг 12.18).

Листинг 12.18. Шаблонные функции

#include <iostream>

template<typename T>
   T sum(T x, T y);

int main() {
   // Явное указание типа T
   std::cout << sum<int>(10, 20) << std::endl;         // 30
   std::cout << sum<double>(10.5, 20.4) << std::endl;  // 30.9
   std::cout << sum<float>(10.5f, 20.7f) << std::endl; // 31.2
   // Автоматическое выведение типа
   std::cout << sum(10, 20) << std::endl;              // 30
   std::cout << sum(10.5, 20.4) << std::endl;          // 30.9
   std::cout << sum(10.5f, 20.7f) << std::endl;        // 31.2
   return 0;
}
template<typename T>
   T sum(T x, T y) {
      return x + y;
   }

Компилятор на основе шаблонной функции и способах ее вызова автоматически создаст следующие перегруженные версии функции:

int sum(int x, int y);
float sum(float x, float y);
double sum(double x, double y);

Шаблонная функция sum() из листинга 12.18 позволяет складывать числа только одного типа. Чтобы можно было складывать числа разных типов, например, int и double, следует объявить два разных обобщенных типа:

template<typename T1, typename T2>
   T1 sum(T1 x, T2 y) {
      return x + y;
   }

Пример вызова функции:

// Явное указание типов T1 и T2
std::cout << sum<double, int>(10.5, 20) << std::endl;  // 30.5
// Автоматическое выведение типа
std::cout << sum(10.5, 20) << std::endl;               // 30.5

Обобщенные и реальные типы можно смешивать в объявлении параметров шаблонной функции, кроме того шаблонные функции допускается перегружать. Существуют два способа перегрузки. Первый способ заключается в определении обычной функции с конкретными параметрами. Определение функции во втором случае выглядит так:

template<>
Тип Название_функции<Тип1, ..., ТипN>(Тип1 Название_переменной1
                              [, ..., ТипN Название_переменнойN])
{
    Тело_функции
   [return[ Возвращаемое_значение];]
}

После ключевого слова template указываются пустые угловые скобки, а после названия функции внутри угловых скобок через запятую перечисляются реальные типы данных. Пример перегрузки шаблонных функций приведен в листинге 12.19.

Листинг 12.19. Перегрузка шаблонных функций

#include <iostream>

template<typename T1, typename T2>
   T1 sum(T1 x, T2 y);
int sum(int x, int y);
template<>
   double sum<double, double>(double x, double y);
template<typename T1, typename T2>
   T1 sum(T1 x, T2 y, int z);
template<typename T, int y>
   T sum(T x);

int main() {
   std::cout << sum(10, 20) << std::endl;        // 30
   std::cout << sum(10.5, 20.4) << std::endl;    // 30.9
   std::cout << sum(10.5f, 20.7f) << std::endl;  // 31.2
   std::cout << sum(10.5, 20) << std::endl;      // 30.5
   std::cout << sum(10.5, 2.3, 10) << std::endl; // 22.8
   // Передача значения через список обобщенных типов
   std::cout << sum<int, 20>(10) << std::endl;   // 30
   return 0;
}
template<typename T1, typename T2>
   T1 sum(T1 x, T2 y) {
      return x + y;
   }
int sum(int x, int y) {                             // Способ 1
   return x + y;
}
template<>
   double sum<double, double>(double x, double y) { // Способ 2
      return x + y;
   }
// Перегрузка и смешивание обобщенных и явных типов
template<typename T1, typename T2>
   T1 sum(T1 x, T2 y, int z) {
      return x + y + z;
   }
// Передача значения через список обобщенных типов
template<typename T, int y>
   T sum(T x) {
      return x + y;
   }

В качестве типа возвращаемого значения мы указали обобщенный тип T1. Если в первом параметре передать число типа double, а во втором — число типа int, то все будет нормально. Тип int будет преобразован в тип double, затем выполнится сложение чисел и результатом станет число типа double:

std::cout << sum(10.5, 20) << std::endl; // 30.5

Если же в первом параметре передать число типа int, а во втором — число типа double, то результатом выполнения функции станет число типа int. Так как тип double не может быть преобразован в тип int без потерь, компилятор выведет предупреждающее сообщение:

std::cout << sum(20, 10.5) << std::endl; // 30
// warning: conversion from 'double' to 'int' may change value

Чтобы тип результата выполнения шаблонной функции соответствовал типу результата вычисления выражения, следует вместо типа возвращаемого значения указать ключевое слово auto, а после закрывающей круглой скобки добавить оператор -> и инструкцию decltype, после названия которой внутри круглых скобок указать выражение (это выражение может отличаться от выражения в инструкции return):

template<typename T1, typename T2>
   auto sum(T1 x, T2 y) -> decltype(x + y) {
      return x + y;
   }

Теперь тип результата всегда будет соответствовать типу результата вычисления выражения:

std::cout << sum(20, 10.5) << std::endl; // 30.5

Начиная со стандарта C++14, достаточно просто указать ключевое слово auto. Тип результата будет выведен компилятором из выражения в инструкции return:

template<typename T1, typename T2>
   auto sum(T1 x, T2 y) {
      return x + y;
   }

При использовании ключевого слова auto учитывается только тип. Объявления const (константа) и ссылки отбрасываются. Если такое поведение нежелательно, то, начиная со стандарта C++14, вместо ключевого слова auto можно указать инструкцию decltype(auto):

template<typename T1, typename T2>
   decltype(auto) sum(T1 x, T2 y) {
      return x + y;
   }
Примечание

Учебник C++ (MinGW-W64)
Учебник C++ (MinGW-W64) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов