Практический курс по ООП C++: https://stepik.org/a/205781
Продолжаем тему
шаблонов функций и давайте обобщим описание шаблона для функции sq_rect следующим
образом:
template <typename T1, typename T2>
auto sq_rect(T1 a, T2 b)
{
return a * b;
}
Смотрите, здесь
у нас два параметра типа T1 и T2, а в качестве
возвращаемого типа записано ключевое слово auto, указывающее
компилятору вычислить его самому на основе значения, которое возвращается
оператором return.
Следует
отметить, что ключевое слово auto в качестве возвращаемого типа допустимо
прописывать, начиная со стандарта C++14. В более ранних стандартах
приходилось использовать те или иные костыли, о которых теперь уже нет смысла
даже вспоминать.
Давайте
посмотрим на варианты вызова этой функции:
int main()
{
int res_1 = sq_rect(5, 6.5); // double sq_rect<int, double>(int a, double b)
double res_2 = sq_rect(5, 6); // int sq_rect<int, int>(int a, int b)
short res_3 = sq_rect<short, short>(2, 3); // int sq_rect<short, short>(short a, short b)
short res_4 = sq_rect<double>(2, 3); // double sq_rect<double, int>(double a, int b)
return 0;
}
Как видите,
теперь в функцию можно передавать аргументы разных типов благодаря
использованию двух параметров T1 и T2 в шаблоне
функции. Результаты формирования перегруженных функций везде ожидаемы, кроме,
может быть функции с двумя типами short. Если вы ожидали,
что возвращаемый тип так же будет иметь тип short, то, как
видите, он был вычислен, как int. И это логично, т.к. возвращается
целочисленное значение, тип которого по умолчанию начинается с int.
Параметры шаблонов с явным указанием типа
При необходимости
мы можем определять параметры шаблонов с некоторыми начальными значениями,
например, следующим образом:
template <typename RT = double, typename T1, typename T2>
RT sq_rect(T1 a, T2 b)
{
return a * b;
}
А, затем,
использовать этот шаблон в функции main:
int main()
{
int res_1 = sq_rect(5, 6.5); // double sq_rect(int a, double b)
double res_2 = sq_rect(5, 6); // double sq_rect(int a, int b)
short res_3 = sq_rect<short>(2, 3); // short sq_rect(short a, short b)
int res_4 = sq_rect<int>(2.3, 3.5); // int sq_rect(double a, double b)
return 0;
}
В результате, мы
получили возможность управлять типом возвращаемого значения, прописывая его,
при необходимости, в угловых скобках при вызове функции. Иногда это бывает
полезно.
Аргументы параметров шаблонов по умолчанию
Помимо
параметров типов, заданных ключевым словом typename, в шаблонах
можно прописывать параметры с явным указанием того или иного типа. Например:
template <int calc_t = 1, typename T1, typename T2>
auto get_rect(T1 width, T2 height)
{
if(calc_t == 1)
return width * height; // площадь
else
return 2 * (width + height); // периметр
}
А, затем, воспользоваться
им следующим образом:
int main()
{
double res_1 = get_rect(5, 6.5); // площадь
int res_2 = get_rect<2>(5, 6); // периметр
std::cout << res_1 << " " << res_2 << std::endl;
return 0;
}
Конечно, это
искусственный, учебный пример для демонстрации возможности использования обычных
переменных в качестве параметров шаблонов. Конечно, в данном случае смысла в
параметре calc_t, как параметра
шаблона, никакого нет, лучше было бы прописать его непосредственно в заголовке
функции:
template <typename T1, typename T2>
auto get_rect(T1 width, T2 height, int calc_t = 1)
{
if(calc_t == 1)
return width * height; // площадь
else
return 2 * (width + height); // периметр
}
И это было бы
правильнее. Однако бывают ситуации, когда такие параметры действительно имеют
смысл. Например, при передаче массива по ссылке и вычисления суммы значений
всех его элементов:
template <typename T, size_t N>
T ar_sum(const T (&ar)[N])
{
std::cout << N << std::endl;
T res = 0;
for(size_t i = 0; i < N; ++i)
res += ar[i];
return res;
}
Когда массив
передается по ссылке, то размер N является частью
его типа и компилятор имеет возможность вычислить значение этого параметра при
инстанцировании шаблона. В результате, этот параметр появляется как
целочисленная переменная N в теле функции ar_sum.
Воспользоваться
этим шаблоном можно следующим образом:
int main()
{
double data[] = {0.5, 3.2, 7.8, 3, 10.4, 5.6};
int marks[] = {2, 2, 3, 2, 3};
auto s = ar_sum(data); // double ar_sum<double, 6>(const double (&ar)[6])
auto s2 = ar_sum(marks); // int ar_sum<int, 5>(const int (&ar)[5])
std::cout << s << std::endl;
return 0;
}
Видите, как
удобно можно передавать в функцию массив любой длины для вычисления его суммы.
Правда, расплачиваться за это приходится генерацией множества перегруженных
функций для массивов разной длины и типов. Поэтому, в общем случае, это не
лучшее решение для вычисления суммы элементов массивов произвольных типов.
Правильнее будет передать дополнительный параметр – длину массива.
Перегрузка шаблонов функций
В заключение
этого занятия отмечу, что шаблоны функций можно также перегружать между собой и
даже с другими отдельными функциями. Например:
template <typename T>
T add(T a, T b) { puts("add: 1"); return a + b; }
template <typename T>
T add(T* a, T* b) { puts("add: 2"); return *a + *b; }
template <typename T1, typename T2>
auto add(T1 a, T2 b) { puts("add: 3"); return a + b; }
void add(std::string& dest, const std::string& src)
{
puts("add: 4");
dest.append(src);
}
Воспользуемся
ими в функции main следующим
образом:
int main()
{
std::string str_1 {"Hello"}, str_2 {"World"};
int a {0}, b{3};
add(&a, &b); // add: 2
add(str_1, str_2); // add: 4
add(1, 2); // add: 1
add(1.3, 2.7); // add: 1
add(1, 2.5); // add: 3
return 0;
}
Правило выбора
того или иного шаблона или функции следующее. Если под типы аргументы подходит
явно объявленная (не шаблонная) функция, то компилятор выбирает именно ее.
Поэтому для строк типа std::string была вызвана
последняя функция с выводом «add: 4». Остальные шаблоны выбираются между
собой в соответствии с типами. Если типы обоих аргументов функции add совпадают, то
вызывается шаблон «add: 1» или «add: 2». Если же типы различаются,
то шаблон «add: 3». Тип
возвращаемого значения здесь не играет никакой роли.
Обратите
внимание, если вместо функции «add: 4» требуется явно вызвать именно
шаблонный вариант, то это можно сделать так:
add<>(str_1, str_2); // add: 1
Здесь угловые
скобки указывают компилятору генерировать (инстанцировать) функцию из
подходящего шаблона.
Практический курс по ООП C++: https://stepik.org/a/205781