Что такое шаблон функции c
Шаблоны на C. Да! На чистом С. Не С++
Зачем?
Давайте представим себе, что нам нужно написать набор функций, которые отличаются друг от друга лишь парой ключевых слов (и, как правило, одно из них — название типа). Ну, вот, например, взгляните на функции, рассчитывающие суммы элементов массивов для разных типов (упрощения ради, проверки указателей на неравенство нулю опущены /*упрощения ради также не рассматривается возможность переполнения для int — прим. пер.*/)
Ну согласитесь же, насколько бы было лучше описать тело функции один раз, указав названия принимаемого (и возвращаемого) типа в виде «параметра», а потом определить экземпляры функции для конкретных типов? И эти функции еще относительно просты, а представьте себе, если бы они были длиннее, а их набор — больше.
Вот именно для этого случая в C++ существует ключевое слово template. Но увы, не в чистом С.
Сейчас вы увидите, что иногда это ключевое слово можно эмулировать средствами старого доброго препроцессора языка С.
Шаблоны в С.
Нам понадобятся некоторые ингридиенты.
1: Заготовки
Для начала, определим пару макросов. Они будут располагаться в отдельном заголовочном файле, и этот файл нам еще понадобится. Для ясности, назовем этот отдельный заголовочный файл «templates.h»
Макрос Template нам понадобится в дальнейшем чтобы объединять макроопределения X и Y в виде X_Y, таким образом, чтобы написав TEMPLATE(function,type) мы получили бы в этом месте function_type.
В препроцессоре С директива ## позволяет объединить два токена в один. Причиной, по которой мы здесь используем два макроса вместо одного #define TEMPLATE(X,Y) X##Y является то, что если X, в свою очередь, тоже будет макроопределением… Впрочем, неважно. Этот вопрос выходит за пределы рассматриваемого в этой статье.
2: Готовим
Наверняка вы уже заметили, что в этом заголовочном файле отсутствует типовая конструкция для защиты от повторного включения #ifndef HEADER_H #define HEADER_H… #endif. И это неспроста, и мы потом еще к этому моменту вернемся. С другой стороны, #ifdef T не то чтобы обязателен, но очень полезен на тот случай, если заголовочный файл включен, а тип не определен. А то сообщения об ошибках могут быть не очень информативными.
3. Сервируем.
Не помню, сколько строк мы до этого написали, но для резюме вы смело можете умножить их число на 3. Или 4?
Мелочи жизни: для GCC 3 строки #ifdef T #undef T #endif можно заменить на одну #undef T, но вот Visual C++ (как минимум, до 7 версии включительно, не переносит подобных вольностей)
Ну, и Хииииииииидер!
Теперь должно быть понятно, почему мы не защищали sum_as_template.h от множественных включений: мы включаем его по разу на каждый задействованный тип.
4. Подаем
Ну, собственно, и все. Можно вызывать:
Пытливый читатель спросит переводчика, а что если мне нужен тип «unsigned long long»? Ведь у нас получится функция «void sum_unsigned long long()»? К счастью для переводчика, автор предусмотрел и это. Используйте typedef:
(Это довольно-таки вольный перевод. Свою статью писать уже лень, раз гугл знает ответ на вопрос function template in plain c, и к той статье мне решительно нечего добавить, но раз на хабре и вообще в русскоязычном секторе ответ гуглом не находится, чтобы добру не пропадать, опубликую пост-мортем)
Шаблоны в C++
В этой статье вы узнаете о шаблонах в C++. Вы научитесь использовать возможности шаблонов для общего программирования.
Шаблоны – это мощные возможности С++, которые позволяют писать общие программы. Проще говоря, вы можете создать одну функцию или класс для работы с разными типами данных с помощью шаблонов.
Шаблоны часто используются в более крупной кодовой базе с целью повторного использования кода и гибкости программ.
Концепцию шаблонов можно использовать двумя разными способами:
Шаблоны функций
Шаблон функции в C++ работает аналогично обычной функции, с одним ключевым отличием.
Один шаблон функции может работать с разными типами данных одновременно, но одна обычная функция может работать только с одним набором типов данных.
Если вам нужно выполнить идентичные операции с двумя или более типами данных, вы используете перегрузку функций, чтобы создать две функции с требуемым объявлением функции.
Однако лучшим подходом было бы использовать шаблоны функций, потому что вы можете выполнять ту же задачу, написав меньше кода.
Как объявить шаблон функции?
Шаблон функции начинается с ключевого слова template, за которым следует параметр /s шаблона внутри <>, за которым следует объявление функции.
В приведенном выше коде T – это аргумент шаблона, который принимает разные типы данных (int, float), а class – это ключевое слово.
Вы также можете использовать ключевое слово typename вместо class в приведенном выше примере.
Когда аргумент типа данных передается в someFunction(), компилятор генерирует новую версию someFunction() для данного типа данных.
Пример 1: для поиска наибольшего числа
Программа для отображения наибольшего из двух чисел с использованием шаблонов функций:
В приведенной выше программе определен шаблон функции Large(), который принимает два аргумента n1 и n2 с типом данных T. Который означает, что аргумент может быть любого типа данных.
Функция Large() возвращает наибольший из двух аргументов с помощью простой условной операции.
Внутри функции main() объявляются переменные трех разных типов данных: int, float и char. Затем переменные передаются в шаблон функции Large(), как обычные функции.
Во время выполнения, когда целое число передается в функцию шаблона, компилятор знает, что он должен сгенерировать функцию Large(), чтобы принять аргументы типа int.
Точно так же, когда передаются данные с плавающей запятой и данные char, он знает типы данных аргументов и соответственно генерирует функцию Large().
Таким образом, использование только одного шаблона функции заменяет три идентичные обычные функции и делает ваш код поддерживаемым.
Пример 2: обмен данными с использованием шаблонов функций
Программа для обмена данными с использованием шаблонов функций.
В этой программе вместо вызова функции путем передачи значения выполняется вызов по ссылке.
Шаблон функции Swap() принимает два аргумента и меняет их местами по ссылке.
Шаблоны классов
Как и шаблоны функций, вы также можете создавать шаблоны классов в C++ для общих операций.
Иногда вам нужна реализация класса, которая одинакова для всех классов, отличаются только используемые типы данных.
Обычно вам нужно создать отдельный класс для каждого типа данных или создать разные переменные-члены и функции в одном классе.
Это приведет к излишнему раздуванию базы кода и трудной поддержке, поскольку изменение состоит в том, что один класс или функция должны выполняться для всех классов или функций.
Однако шаблоны классов позволяют повторно использовать один и тот же код для всех типов данных.
Как объявить?
В приведенном выше объявлении, T – это аргумент шаблона, который является заполнителем для используемого типа данных.
Внутри тела класса переменная-член var и функция-член someOperation() имеют тип T.
Как создать объект?
Чтобы создать объект шаблона класса в С++, вам необходимо определить тип данных внутри <> при создании.
Пример 3: класс Calculator
Программа для сложения, вычитания, умножения и деления двух чисел с использованием шаблона класса:
В приведенной выше программе объявлен шаблон класса Calculator.
Он также содержит общедоступные функции-члены для вычисления сложения, вычитания, умножения и деления чисел, которые возвращают значение типа данных, определенного пользователем. Точно так же используется функция displayResult() для вывода на экран окончательного результата.
В функции main() для типов данных создаются два разных объекта Calculator intCalc и floatCalc: int и float соответственно. Значения инициализируются с помощью конструктора.
Обратите внимание, что мы используем и при создании объектов. Они сообщают компилятору тип данных, используемый для создания класса. Это создает определение класса для каждого типа int и float, которые затем используются соответственно. Затем вызывается displayResult() обоих объектов, который выполняет операции калькулятора и отображает результат.
Шаблоны (справочник по C#)
Возможность использовать сопоставление шаблонов впервые появилась в C# 7.0. С тех пор в каждой новой основной версии C# возможности сопоставления шаблонов расширяются. Сопоставление шаблонов поддерживают следующие выражения и операторы C#:
В этих конструкциях можно сравнить входное выражение с любым из следующих шаблонов:
Логические шаблоны, шаблоны свойств и позиционные шаблоны являются рекурсивными шаблонами. То есть, они могут содержать вложенные шаблоны.
Пример использования этих шаблонов для создания управляемого данными алгоритма можно посмотреть в разделе Учебник: использование сопоставления шаблонов для создания управляемых типами и управляемых данными алгоритмов.
Шаблоны объявления и шаблоны типов
Шаблоны объявления и шаблоны типов используются для проверки того, совместим ли с указанным типом тип определенного выражения в среде выполнения. С помощью шаблона объявления можно также объявить новую локальную переменную. Если шаблон объявления соответствует выражению, этой переменной присваивается результат преобразованного выражения, как показано в следующем примере:
Начиная с C# 7.0, шаблон объявления с типом T соответствует выражению в том случае, если результат выражения имеет значение, отличное от NULL, и выполняется любое из следующих условий:
В следующем примере показаны два последних условия:
Начиная с C# 9.0, для этой цели можно использовать шаблон типа, как показано в следующем примере:
Как и шаблон объявления, шаблон типа соответствует выражению, если результат выражения не равен NULL, а его тип в среде выполнения удовлетворяет любому из указанных выше условий.
Дополнительные сведения см. в разделах Шаблон объявления и Шаблон типа в примечаниях к предлагаемой функции.
Шаблон константы
Начиная с C# 7.0, вы можете использовать шаблон константы для проверки того, равен ли результат выражения заданной константе, как показано в следующем примере:
В шаблоне константы можно использовать любое константное выражение, например:
Начиная с C# 9.0, вы можете использовать шаблон константы null с отрицанием для проверки неравенства значению NULL, как показано в следующем примере:
Дополнительные сведения см. в разделе Шаблон константы в примечании к предлагаемой функции.
Реляционные шаблоны
Начиная с C# 9.0, вы можете использовать реляционный шаблон для сравнения результата выражения с константой, как показано в следующем примере:
Чтобы проверить, находится ли результат выражения в определенном диапазоне, сопоставьте его с шаблоном конъюнкции ( and ), как показано в следующем примере:
Если результат выражения — null или его не удается преобразовать в тип константы с помощью преобразования, допускающего значение NULL, или распаковки-преобразования, то реляционный шаблон не соответствует выражению.
Дополнительные сведения см. в разделе Реляционные шаблоны в примечании к предлагаемой функции.
Логические шаблоны
Как показано в предыдущем примере, блоки объединения в шаблоне можно использовать многократно.
Дополнительные сведения см. в разделе Блоки объединения шаблонов в примечании к предлагаемой функции.
Шаблон свойства
Начиная с C# 8.0, вы можете использовать шаблон свойства для сопоставления свойств или полей выражения с вложенными шаблонами, как показано в следующем примере:
Шаблон свойства соответствует выражению, если результат выражения не равен NULL, а каждый вложенный шаблон соответствует соответствующему свойству или полю результата выражения.
Вы также можете добавить проверку типа среды выполнения и объявление переменной в шаблон свойства, как показано в следующем примере:
Шаблон свойства является рекурсивным шаблоном. Это значит, что любой шаблон можно использовать как вложенный шаблон. Используйте шаблон свойства для сопоставления элементов данных с вложенными шаблонами, как показано в следующем примере:
В предыдущем примере используются две возможности, доступные в C# 9.0 и более поздних версиях языка: блок объединения шаблонов or и типы записей.
Начиная с C# 10 можно ссылаться на вложенные свойства или поля в шаблоне свойства. Например, можно выполнить рефакторинг метода из предыдущего примера в следующий эквивалентный код:
Дополнительные сведения см. в разделе Шаблон свойства в примечании к предлагаемой функции и примечание к предлагаемой функции Расширенные шаблоны свойств.
Позиционный шаблон
Начиная с C# 8.0, вы можете использовать позиционный шаблон для деконструкции результата выражения и сравнения результирующих значений с соответствующими вложенными шаблонами, как показано в следующем примере:
В предыдущем примере тип выражения содержит метод Deconstruct, используемый для деконструкции результата выражения. Можно также сопоставлять выражения кортежных типов с позиционными шаблонами. Таким образом можно сопоставить несколько входных значений с различными шаблонами, как показано в следующем примере:
В предыдущем примере используются реляционные и логические шаблоны, доступные в C# 9.0 и более поздних версиях языка.
Можно также расширить позиционный шаблон одним из следующих способов:
Добавьте проверку типа среды выполнения и объявление переменной, как показано в следующем примере:
Используйте шаблон свойства в позиционном шаблоне, как показано в следующем примере:
Можно объединить два предыдущих варианта, как показано в следующем примере:
Позиционный шаблон является рекурсивным шаблоном. Это значит, что любой шаблон можно использовать как вложенный шаблон.
Дополнительные сведения см. в разделе Позиционный шаблон в примечании к предлагаемой функции.
Шаблон var
В шаблоне var тип объявленной переменной равен установленному во время компиляции типу выражения, сопоставляемого с данным шаблоном.
Дополнительные сведения см. в разделе Шаблон var в примечании к предлагаемой функции.
Шаблон пустой переменной
В предыдущем примере шаблон пустой переменной используется для обработки значения null и любых целочисленных значений, которые не соответствуют имеющимся членам перечисления DayOfWeek. Благодаря этому гарантируется, что выражение switch в приведенном примере сможет обработать все возможные входные значения. Если в выражении switch не используется шаблон пустой переменной и при этом ни один из шаблонов выражения не соответствует входным данным, среда выполнения генерирует исключение. Если выражение switch не обрабатывает все возможные входные значения, компилятор генерирует предупреждение.
Дополнительные сведения см. в разделе Шаблон пустой переменной в примечании к предлагаемой функции.
Шаблон в круглых скобках
Начиная с C# 9.0, вы можете использовать круглые скобки вокруг любого шаблона. Как правило, это делается для того, чтобы подчеркнуть или изменить приоритет логических шаблонов, как показано в следующем примере:
Спецификация языка C#
Дополнительные сведения см. в следующих примечаниях к предлагаемой функции.
Введение в магию шаблонов
Зачем?
Мы используем шаблоны для красоты. Каждый С++ разработчик знает, что такое красота, красота — это когда код компактный, понятный и быстрый.
Мета-магия и неявные интерфейсы
Что такое метопрограмма? Метопрограмма — это программа, результатом работы которой будет другая программа. Для С++ выполнением метапрограмм занимается компилятор, а результатом является бинарный файл.
Именно для написания метапрограмм используются шаблоны.
Чем еще отличается полиморфизм шаблонов от полиморфизма виртуальных функций? Если класс обладает явным интрерфейсом, который мы определили в объявлении класса, то далее в программе объекты этого типа могут использоваться в соответствии с этим самым интерфесом. А вот для шаблонов мы используем неявные интерфейсы, т.е. использованием объекта типа мы определяем неявный интерфейс типа, который выведет компилятор при построении метапрограммы.
Первые заклинания: волшебная дубина
Конкретизируем наш шаблон и посмотрим, какие типы мы получили для различных параметров шаблона:
В выводе программы видно, что типы конкретизаций шаблона разные даже для эквивалентных типов — unsigned char & char. При этом они идентичны для char & CHAR, т.к. typedef не создает тип, а лишь дает ему другое имя. Идентичны они и для выражений 1 и 2-1, т.к. компилятор вычисляет выражения и вместо 2-1 использует 1.
Отсюда и вытекает, что мы не можем использовать для шаблонов раздельную компиляцию без дополнительных проблем:
Вообще, в стандарте С++ для этого есть ключевое слово export, однако эта фича слишком труднореализуема и отсутствует в большинстве компиляторов. Есть компиляторы, которые ее поддерживают, но не советую ее использовать в переносимом коде.
Кроме классов существуют и шаблоны функций:
Если компилятор может вывести тип параметра шаблона из типа параметров — он так и поступит, при этом нам не нужно указывать его в коде. Если нет, то мы можем определить разрешающую функцию:
Она не несет никаких накладных расходов.
Специализация — это новый уровень
Обычно используя шаблоны мы хотим написать универсальный код, однако в некоторых случаях мы можем проиграть в производительности. Для решения проблемы существует специальное заклятие — специализация шаблона. Специализация — это повторное определение шаблона с конкретным типом либо классом типов:
Компилятор сам выберет наиболее точно подходящую специализацию, в примере это класс типов “указатель на тип”.
Зловещая магия: рекурсия
Специализации и тот факт, что мы можем использовать шаблоны в шаблонах, дает дам одну очень интересную возможность — рекурсия времени компиляции.
Самый простой и популярный пример — вычисление какого-либо ряда или полинома, скажем, сумма ряда натуральных чисел:
Смотрим… Работает! Круто? Увеличим количество итераций до 500:
Теперь компиляция занимает больше времени, при этом время выполнения программы — константа! Чудеса!
Не делай козу если хотел грозу
Тут есть пара моментов.
Максимальная глубина рекурсии по умолчанию ограничена реализацией, для нового gcc это 900, для старых версий он меньше. Параметр
снимает это ограничение.
Второй подводный камень — не ждите отчетов об ошибках. Меняем сумму на факториал:
Получаем некорректный результат, и ни одного предупреждения.
Третий момент, очевидный: мы можем создать слишком много почти одинаковых конкретизаций шаблона и вместо прироста производительности получить прирост бинарного кода.
Мощные заклинания древних
А можно ли совместить магию наследования с шаблонной магией?
Древние используют для этого заклинание CRTP. Идея проста: применить не виртуальное наследование и обеспечить полиморфное поведение с помощью явного приведения типа наследника к типу родителя. Давайте рассмотрим пример использования:
Мы получаем наследуемые inline методы с полиморфным поведением! Кто скажет что это не круто — мой враг навсегда.
Древние также советуют добавлять в конструктор родителя что-то типа того:
Чтобы демоны, разбуженные мощным заклинанием, не смогли причинить вред вызвавшему их магу.
Есть еще много тайных техник, древних и не очень. Надеюсь на не скорую встречу /*в аду*/, и да прибудет с вами мощь древних.
Универсальные шаблоны позволяют точно настроить метод, класс, структуру или интерфейс в соответствии с типом обрабатываемых данных. Например, вместо использования класса Hashtable, который разрешает ключам и значениям иметь любой тип, можно использовать универсальный класс Dictionary и указать типы, разрешенные для ключа и значения. Помимо прочего, преимуществами универсальных шаблонов являются улучшенная возможность многократного использования кода и сохранения типов.
Определение и использование универсальных шаблонов
Универсальными шаблонами являются классы, структуры, интерфейсы и методы, которые имеют прототипы (параметры типов) для одного или нескольких типов, которые они хранят или используют. Класс универсальной коллекции может использовать параметр типа в качестве заполнителя для типа объектов, которые в нем хранятся. Параметры типа отображаются как типы его полей и типы параметров его методов. Универсальный метод может использовать параметр типа в качестве типа возвращаемого значения или как тип одного из своих формальных параметров. Следующий код иллюстрирует определение простого универсального класса.
При создании экземпляра универсального класса необходимо указать фактические типы для замены параметров типа. При этом создается новый универсальный класс, называемый сконструированным универсальным классом, с выбранными типами, заменяющими все параметры типа. Результатом является типобезопасный класс, соответствующий вашему выбору типов, как показано в следующем коде.
Терминология универсальных шаблонов
Определение универсального типа — это объявление класса, структуры или интерфейса, которое работает в качестве шаблона с прототипами для типов, которые он может содержать или использовать. Например, класс System.Collections.Generic.Dictionary может содержать два типа: ключи и значения. Поскольку определение универсального типа — это только шаблон, создавать экземпляры класса, структуры или интерфейса, являющиеся определением универсального типа, нельзя.
Сконструированный универсальный тип или сконструированный тип является результатом указания типов для параметров универсального типа в определении универсального типа.
Аргумент универсального типа является любым типом, заменяемым на параметр универсального типа.
Общий термин универсальный тип описывает определения как сконструированных типов, так и универсальных типов.
Ковариация и контравариация параметров универсального типа позволяют использовать сконструированные универсальные типы, аргументы типов которых находятся на более высоком (в случае ковариации) или низком (в случае контравариации) уровне иерархии наследования, чем у целевого сконструированного типа. Вместе ковариантность и контрвариантность называются вариацией. Дополнительные сведения см. в разделе Ковариация и контравариантность.
Определение универсального метода — это метод с двумя списками параметров: списком параметров универсальных типов и списком формальных параметров. Параметры типа могут отображаться в качестве возвращаемого типа или в качестве типов формальных параметров, как показано в следующем коде.
Универсальные методы могут присутствовать в универсальных и неуниверсальных типах. Важно отметить, что метод не является универсальным только потому, что он принадлежит универсальному типу или даже в том случае, если он имеет формальные параметры, типы которых являются универсальными параметрами для включающего их типа. Метод является универсальным только в том случае, если он имеет свой собственный список параметров типа. В следующем коде только метод G является универсальным.
Преимущества и недостатки универсальных шаблонов
Использование универсальных коллекций и делегатов предоставляет целый ряд преимуществ.
Типобезопасность. Универсальные шаблоны позволяют передать компилятору обязанности обеспечения типовой безопасности. Нет необходимости написания кода для проверки правильности типа данных, так как проверка происходит во время компиляции. Уменьшается потребность приведения типов и вероятность ошибок во время выполнения.
Объем кода уменьшен и поддерживает многократную реализацию. Нет необходимости наследования базового типа и преобладающих членов. Например, LinkedList готов к немедленному использованию. Например, можно создать связанный список строк со следующим объявлением переменной:
Повышенная производительность. Универсальные типы коллекций имеют более высокую производительность при хранении и управлении типами значений, поскольку отсутствует необходимость их упаковки.
Универсальные шаблоны упрощают динамически создаваемый код. Универсальные делегаты можно также использовать в динамически создаваемом коде без необходимости создания типа делегата. Это увеличивает количество ситуаций, в которых можно использовать облегченные динамические методы вместо создания целых сборок. Дополнительные сведения см. в разделе Практическое руководство. Определение и выполнение динамических методов и DynamicMethod.
Ниже перечислены некоторые ограничения универсальных шаблонов.
Перечисления не могут иметь параметров универсального типа. Перечисление может быть универсальным только случайно (например, если оно является вложенным в универсальный тип, который определен с помощью Visual Basic, C# или C++). Дополнительные сведения см. в разделе «Перечисления» статьи Система общих типов CTS.
Облегченные динамические методы не могут быть универсальными.
В Visual Basic, C# и C++ вложенный тип, заключенный в универсальном типе, не может быть использован для создания объекта, кроме тех случаев, когда типы были назначены параметрам типа всех заключенных типов. Другими словами, вложенный тип, который определен с помощью этих языков, включает параметры типов всех его заключенных типов. Это позволяет использовать параметры типа заключающих типов в определениях членов вложенного типа. Дополнительные сведения см. в подразделе «Вложенные типы» раздела MakeGenericType.
Вложенный тип, который определяется путем создания кода в динамической сборке или с помощью ассемблера IL (Ilasm.exe), не обязательно должен включать параметры его заключающих типов. Тем не менее, если он их не включает, параметры типов находятся вне области вложенного класса.
Дополнительные сведения см. в подразделе «Вложенные типы» раздела MakeGenericType.
Библиотека классов и языковая поддержка
.NET предоставляет ряд универсальных классов коллекций в следующих пространствах имен:
В пространстве имен System.Collections.ObjectModel содержатся дополнительные типы универсальных коллекций (например, универсальный класс ReadOnlyCollection ), которые удобно использовать для предоставления объектных моделей пользователям классов.
Универсальные интерфейсы для реализации сортировки и сравнений на равенство предоставляются в пространстве имен System вместе с универсальными типами делегатов для обработчиков событий, преобразований и предикатов поиска.
В пространство имен System.Reflection добавлена поддержка универсальных шаблонов для обеспечения возможности проверки универсальных типов и универсальных методов, в пространство имен System.Reflection.Emit добавлена поддержка шаблонов для создания динамических сборок, содержащих универсальные типы и методы, в пространство имен System.CodeDom добавлена поддержка шаблонов для создания графов исходного кода, включающих универсальные шаблоны.
Среда CLR предоставляет новые коды операций и префиксы для поддержки универсальных типов в языке MSIL, включая Stelem, Ldelem, Unbox_Any, Constrainedи Readonly.
В языках Visual C++, C# и Visual Basic обеспечивается полноценная поддержка определения и использования универсальных шаблонов. Дополнительные сведения о поддержке языков см. в статьях Универсальные типы в Visual Basic, Введение в универсальные шаблоны и Обзор универсальных типов в Visual C++.
Вложенные типы и универсальные шаблоны
Тип, вложенный в универсальный тип, может зависеть от параметров типа этого универсального типа. Среда CLR рассматривает вложенные типы как универсальные, даже если они не имеют своих собственных параметров универсального типа. При создании экземпляра вложенного типа необходимо задать аргументы типа для всех включающих его универсальных типов.