Эта статья является логическим продолжением статьи Шаблоны функций в С++ для начинающих Первое знакомство. В той статье был использован пример кода программы, где шаблонная функция выбирает большее из двух чисел число:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
usingstd::cout;
usingstd::cin;
template<typenameT1,typenameT2>//Шаблон с двумя параметрами
T1 max(T1a,T2b){//Функция с шаблонными параметрами
if(a>b)returna;
elseif(b>a)returnb;
else{
cout<<"a == b";
returna;
}
}
intmain(){
inti=10;
doubled=66.999;
cout<<max(i,d);//Вызов функции
cin.get();
}
И особенностью этого примера было проведение компилятором С++ неявного преобразования: вместо ожидаемого 66.999 у нас получается 66
В той статье объяснялось, почему. Так вот, иногда это может быть нормально, но из-за того, что мы получаем не то, что ждём, можно говорить об ошибке.
На заметку:
Смешивание разных типов в вычислении может приводить к логическим ошибкам.
Эти логические ошибки возникают в основном из-за незнаний правил неявных преобразований типов в языке С++. Если человек понимает как происходят приведения типов, то он поймёт, где можно смешать типы, а где смешивать не нужно, но если человек не понимает правил неявных преобразований типов, то смешение им типов в одном вычислении с большой вероятностью ни к чему хорошему не приведёт.
Скорее всего человек, написавший показанный код, хотел увидеть именно 66.999, а не усечённое до целого число 66. Будет ли отдано число с типом int или double в нашем примере напрямую — зависит от порядка отдаваемых в функцию аргументов:
C++
1
2
cout<<max(i,d)<<'\n';//Вызов функции ==> 6
cout<<max(d,i)<<'\n';//Вызов функции ==> 6.999
И вот вопрос: "Как используя шаблонную функцию выводить именно то, что логичнее всего ожидать?". Как не зависеть от порядка передаваемых в функцию аргументов?
На заметку:
Параметры шаблонов очень похожи на параметры функций, но в отличие от параметров функций, параметры шаблонов умеют автоматически выводить типы. Иногда имеет смысл помочь автоматике и вручную подсказать правильный тип нужному аргументу. Для этого используются угловые скобки в месте обращения к шаблонной функции, а в угловых скобках пишутся типы, которые будут отдаваться в шаблон. Типы, описываемые в угловых скобках, предназначенные для шаблонных параметров, я буду называть шаблонными аргументами. Шаблонные аргументы уходят в шаблон и параметры, заявленные в шаблоне, поочереди, слева-направо, впитывают в себя эти аргументы.
Например, в показанном примере нас мало устраивает наглость компилятора и вывод на экран значения с усечённым до целого типом. Мы можем вручную подсказать, что мы ожидаем именно double. Для этого нужно использовать шаблонные аргументы.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
usingstd::cout;
usingstd::cin;
template<typenameT1,typenameT2>//Шаблон с двумя параметрами
T1 max(T1a,T2b){//Функция с шаблонными параметрами
if(a>b)returna;
elseif(b>a)returnb;
else{
cout<<"a == b";
returna;
}
}
intmain(){
inti=10;
doubled=66.999;
cout<<max<double>(i,d)<<'\n';//Вызов функции, с засланием шаблону аргумента "double"
cin.get();
}
Теперь всё работает как и должно, double не усекается и на экран выводится число в своём нормальном виде.
Смысл на самом деле очень простой. Всё как в функциях, только если у функций параметры обозначают значения, параметры шаблонов обозначают типы, и аргументы шаблонов можно опускать, если сильно надеяться на автоматический вывод типов, но можно и задавать вручную, подсказывая компилятору действительно нужный тип.
Есть ещё один вариант выхода из сложившейся ситуации с неправильным для нас выводом на экран числа. Если полностью довериться автоматическому выводу типов, то можно использовать не два, а три параметра шаблона: один параметр шаблона непосредственно для типа функции, второй параметр шаблона для одного типа параметра функции и третий параметр для другого типа параметра функции.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
usingstd::cout;
usingstd::cin;
template<typenameT1,typenameT2,typenameFoo_Type>//Шаблон с тремя параметрами
Foo_Type max(T1a,T2b){//Функция с двумя параметрами
if(a>b)returna;
elseif(b>a)returnb;
else{
cout<<"a == b";
returna;
}
}
intmain(){
inti=10;
doubled=66.999;
cout<<max<int,double,double>(i,d)<<'\n';//Вызов функции, отдаём аргументы шаблону явно
cin.get();
}
Параметры шаблона поочереди принимают отданные в шаблон аргументы, слева-направо: в T1 приходит int, в T2 приходит double, в T3 — double. Поскольку функция отдаёт значение с типом T3, а в T3 пришёл тип double, нам и отдаётся значение с типом double, как мы и хотели.
На заметку:
Если все типы, заявленные в шаблоне, могут быть выведены из типов приходящих в параметры функции аргументов, тогда шаблонные аргументы, в момент обращения к шаблонной фунции, можно или опускать полностью, или опускать частично, в противном случае будет необходимо явно указать все типы в аргументах шаблону.
В нашем последнем примере два из трёх типов легко выводятся из значений, отданных вовнутрь функции, но третий тип выводить неоткуда, из-за этого пришлось явно указать все типы. Хотя, если бы были типы, которые могли бы вывестись из значений отданных функции, и они бы были написаны после "смущающего шаблон типа", то вполне достаточным вариантом могло бы быть описание всех типов до последнего "смущающего типа", т. е. немогущего ни из чего вывестись типа.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
usingstd::cout;
usingstd::cin;
template<typenameT1,typenameT2,typenameT3,typenameT4>//Шаблон с четырьмя параметрами
T3 foo(T1,T2,T3,T4){//Функция с четырьмя параметрами
return0;
}
intmain(){
inti=10;
doubled=66.999;
constchar*S="HELLO\n";
charch='!';
cout<<foo<int,double,constchar*>(i,d,S,ch);//4-й шаблонный аргумент (тип в угловых скобках) опущен,
//хотя 4-й аргумент (ch) в функцию уходит
cin.get();
}
Не путайтесь, для шаблонов параметры и аргументы в угловых скобках, а для функций в круглых. В остальном весь принцип достаточно прост: если есть значение, то из него можно вывести тип, ели нет значения, то тип вывести из ниоткуда нельзя. Если есть такой параметр в шаблоне, в который не приходит никакого значения, то все типы, которые должны бы были вывестись самостоятельно, нужно задавать в аргументах шаблону явно, до этого самого типа включительно, значение которого самостоятельно вывестись не способно.
На этом эту статью заканчиваю и надеюсь, что было интересно и познавательно.
Добавить комментарий