Возвращаемый тип шаблонных функций [1]
Шаблонные функции имеют два различных набора параметров:
1) Шаблонные
параметры(Template parameters),
которые объявляются в угловых скобках перед именем шаблонной функции:
template<typename
T> // T шаблонный параметр.
2) Параметры
вызова (Call parameters), которые объявляются в круглых скобках после имени
шаблонной функции:
T max
(T a, T b); // a и b –
параметры вызова.
Вы можете иметь любое количество шаблонных параметров.
Например, вы можете определить шаблон max() для вызова двух потенциально
различных типов:
template<typename
T1, typename T2>
T1 max (T1
a, T2 b)
{
return b < a ? a : b;
}
auto m =
::max(4, 7.2); // OK, но тип первого аргумента
определяет возвращаемый тип
Может быть желательным иметь возможность передавать
различные типы в шаблонную функцию max(), но как показывает это пример – здесь
возникает проблема. Если вы используете один из параметров как возвращаемый
тип, то аргумент второго параметра будет конвертирован в этот тип – независимо
от намерений вызывающей стороны. Таким образом, возвращаемый тип зависит от
порядка вызова аргументов. Максимум 7.2 и 4 будет double 7.2, а максимум 4 и
7.2 – будет int 7.
C++
предоставляет различные пути для решения этой проблемы:
- Ввести третий шаблонный параметр для возвращаемого типа,
- Позволить компилятору найти возвращаемый тип,
- Объявить возвращаемый тип "общим типом” для типов имеющихся двух параметров.
Шаблонные параметры
для возвращаемых типов
Выведение (deduction) шаблонных аргументов позволяет нам
вызывать шаблонные функции с синтаксисом, идентичным синтаксису вызова обычных
функций: мы не должны явно специфицировать типы, соответствующие шаблонным
параметрам.
Однако, мы можем специфицировать для использования в
шаблонных параметрах явно:
template<typename
T>
T max (T a,
T b);
::max<double>(4,
7.2); // инстанцируем T
как double
В случае отсутствия связи между шаблонными параметрами и
параметрами вызова и в случае, когда шаблонные параметры не могут быть
установлены, вы должны специфицировать шаблонные аргументы явно. Например, вы
можете ввести третий шаблонный аргумент для определения возвращаемого типа
шаблонной функции.
template<typename
T1, typename T2, typename RT>
RT max (T1 a, T2 b);
Однако, вывод шаблонных аргументов не берет возвращаемые
типы в расчет и RT не появляется в типах параметров вызова функции. (Выведение
можно рассматривать как часть разрешения перегрузки – процесса, который никоим образом не основывается на выборе возвращаемых типов. Единственное исключение –
возвращаемый тип в операторах-членах преобразования.) Поэтому RT не может быть
выведен.
Следовательно, вы должны специфицировать список шаблонных
аргументов явно. Например:
template<typename
T1, typename T2, typename RT>
RT max (T1
a, T2 b);
::max<int,double,double>(4,
7.2); // OK, но занудно
До сих пор мы рассматривали случаи, когда явным образом
упоминались либо все, либо ни один из аргументов шаблона функции. Другой подход
– специфицировать только первые аргументы явно и позволить выведению обработать
остальные. В общем виде, вы должны специфицировать все типы аргументов до
последнего, который не может быть определён неявно. Таким образом, если мы
изменим порядок шаблонных аргументов в нашем примере, вызывающей стороне
необходимо будет специфицировать только возвращаемый тип:
template<typename
RT, typename T1, typename T2>
RT max (T1
a, T2 b);
::max<double>(4, 7.2); //OK: возвращаемый тип double, T1 и T2 – выводятся
В этом примере, вызов max<double> явно устанавливает
RT в double, но параметры Т1 и Т2 выводятся как int и double из аргументов.
Отметим, что эта модифицированная версия max() не даёт
значительных преимуществ и может быть использована только в простых случаях.
Вывод возвращаемого
типа
Если возвращаемый тип зависит от шаблонных параметров –
простейший и наилучший подход к выведению возвращаемого типа – позволить
компилятору найти его. Начиная с С++14, это возможно через не-декларирование
какого-либо типа (вы должны декларировать возвращаемый тип как auto).
template<typename
T1, typename T2>
auto max (T1 a, T2 b)
{
return b < a ? a : b;
}
auto max (T1 a, T2 b)
{
return b < a ? a : b;
}
Фактически, использование auto для возвращаемого типа без соответствующего
замыкающего возвращаемого типа (trailing return type) (который может быть
введен -> в конце) указывает, что действительный
возвращаемый тип должен быть выведен из выражения return в теле функции. Конечно, выведение возвращаемого типа из тела функции должно быть возможно. Следовательно, код
должен быть доступен и множество выражений return должно соответствовать.
До C++14 возможно только, чтобы компилятор определял тип
возвращаемого значения по своему объявлению, в которое добавлена часть
реализации. В С++11 мы можем получить выгоду из того, что замыкающий
возвращаемый тип позволяет нам использовать параметры вызова. То есть, мы можем объявить, что возвращаемый
тип получен из результата оператора ?:
template<typename
T1, typename T2>
auto max
(T1 a, T2 b) -> decltype(b<a?a:b)
{
return b < a ? a : b;
}
Здесь результирующий тип определяется правилами для
оператора?:, которые достаточно сложны, но обычно дают интуитивно ожидаемый
результат (например, если a и b имеют разные арифметические типы, то для
результата найден общий арифметический тип).
Заметьте что
template<typename
T1, typename T2>
auto max
(T1 a, T2 b) -> decltype(b<a?a:b);
- это объявление, следовательно компилятор использует правила
оператора ?: , вызванного для параметров
a и b , чтобы найти возвращаемый тип max() во время компиляции. Реализация не обязательно должна
соответствовать. Фактически, использование true как условия для оператора ?: в
объявлении достаточно.
template
<typename T1, typename T2>
auto max
(T1 a, T2 b) -> decltype (true? a: b);
Однако, в произвольном случае это определение имеет
значительный недостаток: может случиться, что возвращаемый тип имеет ссылочный
тип, поскольку в некоторых условиях Т может быть ссылкой. В этом случае мы
должны вернуть тип, являющийся продуктом ослабления типа Т (decayed from T)[2],
который выглядит следующим образом:
#include
<type_traits>
template<typename
T1, typename T2>
auto max
(T1 a, T2 b) -> typename std::decay<decltype(true?a:b)>::type
{
return b < a ? a : b;
}
Здесь используется характеристика типа std::decay<>,
которая возвращает результирующий (ослабленный) тип в своем члене type. Это
определено в стандартной библиотеке в <type_traits>
. Поскольку член type является типом, вы должны указать typename для доступа к
нему.
Отметим, что инициализация типа auto всегда ослабляет. Это
также приложимо к возвращаемым значениям типа auto. Пример поведения auto как возвращаемого типа показан
ниже, где a объявлено как ослабленный тип i, int:
int i = 42;
int
const& ir = i; // ir – ссылка на i
auto a = ir; // a объявлена как
новый объект типа int
Возвращаемый тип как общий тип
Начиная с С++11, стандартная библиотека С++ предоставляет
средство для специфицирования выбираемого "более общего типа". std::common_type<>::type[3] даёт в результате "общий тип" для
двух (и более) различных типов,
передаваемых как аргументы шаблона. Например:
#include
<type_traits>
template<typename
T1, typename T2>
std::common_type_t<T1,T2>
max (T1 a, T2 b)
{
return b < a ? a : b;
}
Итак, std::common_type – характеристика типа, определённая
в <type_traits>, которая даёт
структуру, имеющую член type для результирующего типа. Таким образом, его
основное использование выглядит следующим образом:
typename
std::common_type<T1,T2>::type //начиная с C++11
Однако, начиная с С++14, вы можете упростить использование
характеристик, подобной этой, добавляя _t к имени характеристики и отказавшись
от typename и ::type, так, что определение
возвращаемого типа упрощается:
std::common_type_t<T1,T2> // эквивалент, начиная
с C++14
Реализация std :: common_type<> использует некоторые хитрости
программирования шаблонов. Пока на них не останавливаемся. В результате, и
::max(4, 7.2), и ::max(7.2,
4) дают в результате 7.2 типа double. Заметьте, что std::common_type тоже ослабляет тип.
Используемые материалы:
[1] D. Vandevoorde, N.M. Josuttis, D. Gregor "C++ Templates. The Complete Guide. Second Edition", 2018.
[2]http://www.cplusplus.com/reference/type_traits/decay/
[3]http://www.cplusplus.com/reference/type_traits/common_type/
Комментарии
Отправить комментарий