В уроке «8.13 – Шаблоны функций» мы написали шаблон функции для вычисления максимального из двух значений:
#include template T max(T x, T y) < return (x >y) ? x : y; > int main() < std::cout
Теперь рассмотрим следующую аналогичную программу:
#include template T max(T x, T y) < return (x >y) ? x : y; > int main() < std::cout
Вы можете удивиться, обнаружив, что эта программа не компилируется. Вместо этого компилятор выдаст кучу (возможно, сумасшедших) сообщений об ошибках. В Visual Studio автор получил следующее:
Project3.cpp(11,18): error C2672: ‘max’: no matching overloaded function found Project3.cpp(11,28): error C2782: ‘T max(T,T)’: template parameter ‘T’ is ambiguous Project3.cpp(4): message : see declaration of ‘max’ Project3.cpp(11,28): message : could be ‘double’ Project3.cpp(11,28): message : or ‘int’ Project3.cpp(11,28): error C2784: ‘T max(T,T)’: could not deduce template argument for ‘T’ from ‘double’ Project3.cpp(4): message : see declaration of ‘max’
В нашем вызове функции max(2, 3.5) мы передаем аргументы двух разных типов: int и double . Поскольку мы вызываем функцию без использования угловых скобок для указания фактического типа, компилятор сначала проверяет, есть ли совпадение не с шаблоном для max(int, double) . И не находит.
[C++] Шаблонные функции и шаблонные классы
Затем компилятор проверит, сможет ли он найти совпадение с шаблоном функции (используя вывод аргументов шаблона, который мы рассмотрели в уроке «8.14 – Создание экземпляра шаблона функции»). Однако это также не удастся по простой причине: T может представлять только один тип. Для T не существует типа, который позволил бы компилятору создать экземпляр шаблона функции max(T, T) для функции с двумя разными типами параметров. Другими словами, поскольку оба параметра в шаблоне функции относятся к типу T , они должны соответствовать одному и тому же фактическому типу.
Поскольку ни нешаблонных, ни шаблонных совпадений не найдено, вызов функции не удается разрешить, и мы получаем ошибку компиляции.
Вы можете задаться вопросом, почему компилятор не сгенерировал функцию max(double, double) и не использовал затем числовое преобразование для приведения типа аргумента int в double . Ответ прост: преобразование типов выполняется только при разрешении перегрузок функций, а не при выводе аргументов шаблона.
Это отсутствие преобразования типов преднамеренное, по крайней мере, по двум причинам. Во-первых, это помогает упростить задачу: мы либо находим точное соответствие между аргументами вызова функции и параметрами типа шаблона, либо нет. Во-вторых, это позволяет нам создавать шаблоны функций для случаев, когда мы хотим гарантировать, что два или более параметров имеют один и тот же тип (как в примере выше).
Придется найти другое решение. К счастью, мы можем решить эту проблему (по крайней мере) тремя способами.
Использование static_cast для преобразования аргументов в соответствующие типы
Первое решение – возложить бремя преобразования аргументов в соответствующие типы на вызывающего. Например:
Шаблоны функций. Шаблонные функции c++. template typename. template class. Урок #41
#include template T max(T x, T y) < return (x >y) ? x : y; > int main() < // преобразовываем наш int в double, чтобы мы могли вызвать max(double, double) std::cout (2), 3.5)
Теперь, когда оба аргумента имеют тип double , компилятор сможет создать экземпляр max(double, double) , который удовлетворит этот вызов функции.
Однако это решение неудобно и трудночитаемо.
Предоставление фактического типа
Если бы мы написали функцию max(double, double) , не являющуюся шаблоном, то мы могли бы вызвать max(int, double) и позволить правилам неявного преобразования типа преобразовать наш аргумент int в double , чтобы можно было разрешить вызов функции:
#include double max(double x, double y) < return (x >y) ? x : y; > int main() < std::cout
Однако когда компилятор выполняет вывод аргументов шаблона, он не выполняет никаких преобразований типов. К счастью, нам не нужно использовать вывод аргументов шаблона, если мы сразу укажем фактический тип:
#include template T max(T x, T y) < return (x >y) ? x : y; > int main() < // мы предоставили фактический тип double, поэтому // компилятор не будет использовать вывод аргументов шаблона std::cout (2, 3.5)
В приведенном выше примере мы вызываем max(2, 3.5) . Поскольку мы явно указали, что T следует заменить на double , компилятор не будет использовать вывод аргументов шаблона. Вместо этого он просто создаст экземпляр функции max(double, double) , а затем выполнит преобразование типов для любых несовпадающих аргументов. Наш параметр int будет неявно преобразован в double .
Хотя это более читабельно, чем использование static_cast , было бы еще лучше, если бы нам вообще не приходилось думать о типах при вызове функции max .
Шаблоны функций с несколькими параметрами шаблонных типов
Корень нашей проблемы в том, что для нашего шаблона функции мы определили только один шаблонный тип ( T ), а затем указали, что оба параметра должны быть одного и того же типа.
Лучший способ решить эту проблему – переписать наш шаблон функции таким образом, чтобы наши параметры могли вычисляться в разные типы. Вместо того, чтобы использовать один параметр шаблонного типа T , теперь мы будем использовать два ( T и U ):
#include // Мы используем два параметра шаблонных типов с именами T и U template // x может определиться в тип T, а y может определиться в тип U T max(T x, U y) < return (x >y) ? x : y; // упс, здесь у нас проблема сужающегося преобразования > int main()
Поскольку мы определили x с шаблонным типом T , а y с шаблонным типом U , x и y теперь могут определять свои типы независимо. Когда мы вызываем max(2, 3.5) , T может быть int , а U может быть double . Компилятор с радостью создаст для нас экземпляр max(int, double) .
Однако в приведенном выше коде всё еще есть проблема: используя обычные арифметические правила (8.4 – Преобразования при вычислении арифметических выражений), double имеет приоритет над int , поэтому наш условный оператор вернет double . Но наша функция определена как возвращающая T – в тех случаях, когда T определяется в int , наше возвращаемое значение double подвергнется сужающему преобразованию в int , что приведет к предупреждению (и возможной потере данных).
Указание возвращаемого типа U не решает проблемы, поскольку мы всегда можем поменять порядок операндов в вызове функции, чтобы поменять местами типы T и U .
Как решить эту проблему? Это хорошее применение для автоматического типа возвращаемого значения – мы позволим компилятору определить из инструкции return , каким должен быть возвращаемый тип:
#include template auto max(T x, U y) < return (x >y) ? x : y; > int main()
Эта версия max теперь отлично работает с операндами разных типов.
Сокращенные шаблоны функций
C++20 вводит новое использование ключевого слова auto : когда ключевое слово auto используется в качестве типа параметра в обычной функции, компилятор автоматически преобразует эту функцию в шаблон функции, при этом каждый параметр auto становится независимым параметром шаблонного типа. Этот метод создания шаблона функции называется сокращенным шаблоном функции.
auto max(auto x, auto y) < return (x >y) ? x : y; >
является сокращением в C++20 для следующего шаблона:
template auto max(T x, U y) < return (x >y) ? x : y; >
который совпадает с шаблоном функции max , который мы написали выше.
В случаях, когда вы хотите, чтобы каждый параметр шаблонного типа был независимым типом, эта форма предпочтительна, поскольку удаление строки объявления параметров шаблона делает ваш код более кратким и читабельным.
Лучшая практика
Не стесняйтесь использовать сокращенные шаблоны функций, если каждый параметр auto должен быть независимым шаблонным типом (а стандарт языка установлен у вас на C++20 или новее).
Источник: radioprog.ru
Шаблоны функций в С++
Шаблоны функций, своими словами,— это инструкции, согласно которым создаются локальные версии шаблонированной функции для определенного набора параметров и типов данных.
На самом деле, шаблоны функций -это мощный инструмент в С++, который намного упрощает труд программиста. Например, нам нужно запрограммировать функцию, которая выводила бы на экран элементы массива. Задача не сложная! Но, чтобы написать такую функцию, мы должны знать тип данных массива, который будем выводить на экран. И тут нам говорят — тип данных не один, мы хотим, чтобы функция выводила массивы типа int , double , float и char .
Как оказалось, задача усложнилась. И теперь мы понимаем, что нам нужно запрограммировать целых 4 функции, которые выполняют одни и те же действия, но для различных типов данных. Так как мы еще не знакомы с шаблонами функций, мы поступим так: воспользуемся перегрузкой функций.
// перегрузка функции printArray для вывода массива на экран void printArray(const int * array, int count) < for (int ix = 0; ix < count; ix++) cout void printArray(const double * array, int count) < for (int ix = 0; ix < count; ix++) cout void printArray(const float * array, int count) < for (int ix = 0; ix < count; ix++) cout void printArray(const char * array, int count)
Таким образом, мы имеем 4 перегруженные функции, для разных типов данных. Как видите, они отличаются только заголовком функции, тело у них абсолютно одинаковое. Я написал один раз тело функции для типа int и три раза его скопировал для других типов данных.
И, если запустить программу с этими функциями, то она будет исправно работать. Компилятор сам будет определять какую функцию использовать при вызове.
Как видите, кода получилось достаточно много, как для такой простой операции. А что если, нам понадобится запрограммировать алгоритм сортировки в виде функции. Получается, что для каждого типа данных придется свою функцию создавать. То есть, сами понимаете, что один и тот же код будет в нескольких экземплярах, нам это ни к чему. Поэтому в С++ придуман такой механизм — шаблоны функций.
Шаблоны в C++
В этой статье вы узнаете о шаблонах в C++. Вы научитесь использовать возможности шаблонов для общего программирования.
Шаблоны – это мощные возможности С++, которые позволяют писать общие программы. Проще говоря, вы можете создать одну функцию или класс для работы с разными типами данных с помощью шаблонов.
Шаблоны часто используются в более крупной кодовой базе с целью повторного использования кода и гибкости программ.
Концепцию шаблонов можно использовать двумя разными способами:
- шаблоны функций;
- шаблоны классов.
Шаблоны функций
Шаблон функции в C++ работает аналогично обычной функции, с одним ключевым отличием.
Один шаблон функции может работать с разными типами данных одновременно, но одна обычная функция может работать только с одним набором типов данных.
Если вам нужно выполнить идентичные операции с двумя или более типами данных, вы используете перегрузку функций, чтобы создать две функции с требуемым объявлением функции.
Однако лучшим подходом было бы использовать шаблоны функций, потому что вы можете выполнять ту же задачу, написав меньше кода.
Как объявить шаблон функции?
Шаблон функции начинается с ключевого слова template, за которым следует параметр /s шаблона внутри <>, за которым следует объявление функции.
template class T> T someFunction(T arg)
В приведенном выше коде T – это аргумент шаблона, который принимает разные типы данных (int, float), а class – это ключевое слово.
Вы также можете использовать ключевое слово typename вместо class в приведенном выше примере.
Когда аргумент типа данных передается в someFunction(), компилятор генерирует новую версию someFunction() для данного типа данных.
Пример 1: для поиска наибольшего числа
Программа для отображения наибольшего из двух чисел с использованием шаблонов функций:
// If two characters are passed to function template, character with larger ASCII value is displayed. #include using namespace std; // template function template T Large(T n1, T n2) < return (n1 >n2) ? n1 : n2; > int main() < int i1, i2; float f1, f2; char c1, c2; cout > i1 >> i2; cout > f1 >> f2; cout > c1 >> c2; cout
Enter two integers: 5 10 10 is larger. Enter two floating-point numbers: 12.4 10.2 12.4 is larger. Enter two characters: z Z z has larger ASCII value.
В приведенной выше программе определен шаблон функции Large(), который принимает два аргумента n1 и n2 с типом данных T. Который означает, что аргумент может быть любого типа данных.
Виртуальные функции в C++
Функция Large() возвращает наибольший из двух аргументов с помощью простой условной операции.
Внутри функции main() объявляются переменные трех разных типов данных: int, float и char. Затем переменные передаются в шаблон функции Large(), как обычные функции.
Во время выполнения, когда целое число передается в функцию шаблона, компилятор знает, что он должен сгенерировать функцию Large(), чтобы принять аргументы типа int.
Точно так же, когда передаются данные с плавающей запятой и данные char, он знает типы данных аргументов и соответственно генерирует функцию Large().
Таким образом, использование только одного шаблона функции заменяет три идентичные обычные функции и делает ваш код поддерживаемым.
Пример 2: обмен данными с использованием шаблонов функций
Программа для обмена данными с использованием шаблонов функций.
#include using namespace std; template void Swap(T n1, T n2) < T temp; temp = n1; n1 = n2; n2 = temp; >int main() < int i1 = 1, i2 = 2; float f1 = 1.1, f2 = 2.2; char c1 = ‘a’, c2 = ‘b’; cout Before passing data to function template. i1 = 1 i2 = 2 f1 = 1.1 f2 = 2.2 c1 = a c2 = b After passing data to function template. i1 = 2 i2 = 1 f1 = 2.2 f2 = 1.1 c1 = b c2 = a
В этой программе вместо вызова функции путем передачи значения выполняется вызов по ссылке.
Базовый ввод и вывод в C++
Шаблон функции Swap() принимает два аргумента и меняет их местами по ссылке.
Шаблоны классов
Как и шаблоны функций, вы также можете создавать шаблоны классов в C++ для общих операций.
Иногда вам нужна реализация класса, которая одинакова для всех классов, отличаются только используемые типы данных.
Обычно вам нужно создать отдельный класс для каждого типа данных или создать разные переменные-члены и функции в одном классе.
Это приведет к излишнему раздуванию базы кода и трудной поддержке, поскольку изменение состоит в том, что один класс или функция должны выполняться для всех классов или функций.
Однако шаблоны классов позволяют повторно использовать один и тот же код для всех типов данных.
Как объявить?
template class T> class className < . .. . public: T var; T someOperation(T arg); . .. . >;
В приведенном выше объявлении, T – это аргумент шаблона, который является заполнителем для используемого типа данных.
Внутри тела класса переменная-член var и функция-член someOperation() имеют тип T.
Как создать объект?
Чтобы создать объект шаблона класса в С++, вам необходимо определить тип данных внутри <> при создании.
className classObject;
className classObject; className classObject; className classObject;
Пример 3: класс Calculator
Программа для сложения, вычитания, умножения и деления двух чисел с использованием шаблона класса:
#include using namespace std; template class Calculator < private: T num1, num2; public: Calculator(T n1, T n2) < num1 = n1; num2 = n2; >void displayResult() < cout T add() < return num1 + num2; >T subtract() < return num1 — num2; >T multiply() < return num1 * num2; >T divide() < return num1 / num2; >>; int main() < CalculatorintCalc(2, 1); Calculator floatCalc(2.4, 1.2); cout
Int results: Numbers are: 2 and 1. Addition is: 3 Subtraction is: 1 Product is: 2 Division is: 2 Float results: Numbers are: 2.4 and 1.2. Addition is: 3.6 Subtraction is: 1.2 Product is: 2.88 Division is: 2
В приведенной выше программе объявлен шаблон класса Calculator.
Классы и объекты на C++
Класс содержит два частных членов типа Т: num1 , num2 и конструктор для инициализации членов.
Он также содержит общедоступные функции-члены для вычисления сложения, вычитания, умножения и деления чисел, которые возвращают значение типа данных, определенного пользователем. Точно так же используется функция displayResult() для вывода на экран окончательного результата.
В функции main() для типов данных создаются два разных объекта Calculator intCalc и floatCalc: int и float соответственно. Значения инициализируются с помощью конструктора.
Обратите внимание, что мы используем и при создании объектов. Они сообщают компилятору тип данных, используемый для создания класса. Это создает определение класса для каждого типа int и float, которые затем используются соответственно. Затем вызывается displayResult() обоих объектов, который выполняет операции калькулятора и отображает результат.
- Цикл for в C++
- Преобразование типов в C++
- Оператор if … else в C++
Источник: calmsen.ru