Несколько параметров шаблона
Чтобы функция принимала аргументы разных типов, объявим два параметра шаблона:
template <typename T1, typename T2>
T1 max_value(T1 a, T2 b)
{
return b < a ? a : b;
}
T1 выводится из первого аргумента, T2 из второго. Типы могут различаться или совпадать:
max_value(4, 7.2); // T1 = int, T2 = double
max_value(4.5, 7); // T1 = double, T2 = int
max_value(4, 7); // T1 = int, T2 = int
Оба типа можно задать явно. Можно также указать только первый тип, а второй оставить для вывода компилятору:
max_value<int, double>(4, 7.2); // Оба типа заданы явно
max_value<double>(4, 7.2); // T1 задан, T2 выводится
Во втором вызове T1 равен double. Тип T2 выводится как double из аргумента 7.2. Значение 4 преобразуется в double при передаче в функцию.
Зависимость результата от порядка аргументов
Функция возвращает T1, поэтому перестановка аргументов может изменить результат:
auto x = max_value(4, 7.2); // int: 7
auto y = max_value(7.2, 4); // double: 7.2
В первом вызове условное выражение b < a ? a : b имеет тип double и значение 7.2. Функция возвращает int, поэтому дробная часть отбрасывается.
Во втором вызове T1 равен double. Значение 7.2 возвращается без потери точности.
Чтобы порядок аргументов не определял возвращаемый тип, реализацию функции нужно изменить.
Отдельный параметр для возвращаемого типа
Параметр шаблона RT позволяет задать тип результата отдельно:
template <typename RT, typename T1, typename T2>
RT max_value(T1 a, T2 b)
{
return b < a ? a : b;
}
Возвращаемый тип указывается при вызове функции:
auto x = max_value<double>(4, 7.2);
// RT = double
// T1 = int
// T2 = double
// x = 7.2
RT расположен первым, поэтому его можно указать отдельно. Типы T1 и T2 по-прежнему выводятся из аргументов.
Без явного указания RT код не скомпилируется:
double x = max_value(4, 7.2); // Ошибка: невозможно вывести RT
RT встречается только в возвращаемом типе функции. Тип переменной x не участвует в выводе параметров шаблона.
Выведение возвращаемого типа через auto
Начиная с C++14 компилятор может вывести возвращаемый тип из выражения в операторе return:
template <typename T1, typename T2>
auto max_value(T1 a, T2 b)
{
return b < a ? a : b;
}
Условное выражение с операндами типов int и double имеет тип double. Если оба операнда имеют тип int, результат также имеет тип int:
auto x = max_value(4, 7.2); // double: 7.2
auto y = max_value(7.2, 4); // double: 7.2
auto z = max_value(4, 7); // int: 7
Для int и double порядок аргументов больше не влияет на тип результата.
Несколько операторов return
Два оператора return ведут себя иначе:
template <typename T1, typename T2>
auto max_value(T1 a, T2 b)
{
if (b < a)
{
return a;
}
return b;
}
При T1 = int и T2 = double эта версия не скомпилируется. Первый оператор возвращает int, второй возвращает double. При выводе возвращаемого типа все операторы return должны давать один и тот же тип.
Возвращаемый тип через decltype
decltype определяет тип выражения, не вычисляя его. Возвращаемый тип можно записать после списка параметров. Такая форма называется trailing return type:
template <typename T1, typename T2>
auto max_value(T1 a, T2 b)
-> decltype(b < a ? a : b)
{
return b < a ? a : b;
}
Имена a и b доступны после списка параметров, поэтому их можно использовать в выражении для decltype.
Параметры передаются по значению и уничтожаются после завершения функции. Возвращённая ссылка будет указывать на уже уничтоженный объект и станет висячей.
Удаление ссылки с помощью std::decay_t
std::decay_t удаляет ссылку и позволяет вернуть значение:
#include <type_traits>
template <typename T1, typename T2>
auto max_value(T1 a, T2 b)
-> std::decay_t<decltype(b < a ? a : b)>
{
return b < a ? a : b;
}
Для используемых типов преобразования выглядят так:
std::decay_t<int&> // int
std::decay_t<double> // double
При двух аргументах типа int функция возвращает int. При сочетании int и double она возвращает double.
Обычный auto также выводит возвращаемый тип без ссылки. С decltype(auto) ссылка сохраняется, поэтому для двух аргументов типа int функция снова будет возвращать int&.
Общий тип через std::common_type_t
std::common_type_t определяет общий тип по T1 и T2:
#include <type_traits>
template <typename T1, typename T2>
std::common_type_t<T1, T2> max_value(T1 a, T2 b)
{
return b < a ? a : b;
}
Для числовых типов результат будет следующим:
using A = std::common_type_t<int, double>; // double
using B = std::common_type_t<double, int>; // double
using C = std::common_type_t<int, int>; // int
std::common_type_t<T1, T2> представляет собой сокращённую запись:
typename std::common_type<T1, T2>::type
Для этих типов общий тип определяется по правилам условного оператора с последующим применением преобразований, аналогичных decay.
Некоторые пары типов не имеют общего типа. Для них использовать std::common_type_t не получится.
Выбор способа
Сравнение
autoподходит, если тип результата должен выводиться из выражения вreturn.std::common_type_tполезен, если общий тип нужен и в других объявлениях внутри шаблона.- Параметр
RTпозволяет вызывающему коду выбрать возвращаемый тип. decltypeсохраняет категорию значения и ссылки, поэтому при возврате по значению может потребоватьсяstd::decay_t.
В данной функции достаточно auto:
template <typename T1, typename T2>
auto max_value(T1 a, T2 b)
{
return b < a ? a : b;
}