Возвращаемый тип шаблонных функций [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 для возвращаемого типа без соответствующего замыкающего возвращаемого типа (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/

 

Комментарии

Популярные сообщения из этого блога

Полезные новации С++17. Выражения свертки (Fold expressions)

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

Поддержка декомпозиции при объявлении (structural bindings) для классов