Шаблонные параметры шаблонов c

Урок №173. Шаблоны функций

На предыдущих уроках мы рассматривали, как с помощью функций и классов сделать программы удобнее, безопаснее и производительнее.

Шаблоны функций

Хотя функции и классы являются мощными и гибкими инструментами для эффективного программирования, в некоторых случаях они ограничены из-за требования C++ указывать типы всех используемых параметров. Например, предположим, что нам нужно написать функцию для вычисления наибольшего среди двух чисел:

Всё отлично до тех пор, пока мы работаем с целочисленными значениями. А что, если нам придется работать и со значениями типа double? Вы, вероятно, решите перегрузить функцию max() для работы с типом double:

А это, в свою очередь, головная боль для программистов, так как поддерживать такой код непросто как по затраченным усилиям, так и по времени. И самое важное то, что это нарушает одну из концепций эффективного программирования — сократить до минимума дублирование кода. Правда, было бы неплохо написать одну версию функции max(), которая работала бы с параметрами ЛЮБОГО типа?

Это возможно. Добро пожаловать в мир шаблонов!

Если посмотреть определение слова «шаблон» в словаре, то увидим следующее: «Шаблон — это образец, по которому изготавливаются похожие изделия». Например, шаблоном является трафарет — объект (например, пластинка), в котором прорезан рисунок/узор/символ. Если приложить трафарет к другому объекту и распылить краску, то получим этот же рисунок, прилагая минимум усилий, быстро и, что не менее важно, мы сможем сделать десятки этих рисунков разных цветов! При этом нам нужен лишь один трафарет и нам не нужно определять цвет рисунка заранее (до использования трафарета).

В языке C++ шаблоны функцийэто функции, которые служат образцом для создания других подобных функций. Главная идея — создание функций без указания точного типа(ов) некоторых или всех переменных. Для этого мы определяем функцию, указывая тип параметра шаблона, который используется вместо любого типа данных. После того, как мы создали функцию с типом параметра шаблона, мы фактически создали «трафарет функции».

При вызове шаблона функции, компилятор использует «трафарет» в качестве образца функции, заменяя тип параметра шаблона на фактический тип переменных, передаваемых в функцию! Таким образом, мы можем создать 50 «оттенков» функции, используя всего лишь один шаблон!

Создание шаблонов функций

Сейчас вам, вероятно, интересно, как создаются шаблоны функций в языке C++. Оказывается, это не так уж и сложно. Рассмотрим еще раз целочисленную версию функции max():

Источник

Основы шаблонов С++: шаблоны функций

Дисклаймер: статья была начата еще в феврале, но, по зависящим от меня причинам, закончена не была. Тема очень обширна, поэтому публикуется в урезанном виде. Что не поместилось, будет рассмотрено позже.

image loader

Невозможно разбираться в современном С++, не зная, что такое шаблоны программирования. Данное свойство языка открывает широкие возможности оптимизации и повторного использования кода. В данной статье попробуем разобраться, что это такое и как это всё работает.

Механизм шаблонов в языке С++ позволяет решать проблему унификации алгоритма для различных типов: нет необходимости писать различные функции для целочисленных, действительных или пользовательских типов – достаточно составить обобщенный алгоритм, не зависящий от типа данных, основывающийся только на общих свойствах. Например, алгоритм сортировки может работать как с целыми числами, так и с объектами типа «автомобиль».

Существуют шаблоны функций и шаблоны классов.

Рассмотрим более подробно шаблоны функций.

Шаблоны функций

Как написать первую шаблонную функцию?

Рассмотрим случай определения минимального элемента из двух. В случае целых и действительных чисел придется написать 2 функции.

Можно, конечно, реализовать только одну функцию, с действительными параметрами, но для понимания шаблонов это будет вредным.

Что произойдёт в случае компиляции приложения? Обе реализации функции попадут в бинарный код приложения, даже если они не используются (впрочем, сейчас компиляторы очень умные, умеют вырезать неиспользуемый код). А если необходимо добавить функцию, определяющую минимальную из 2 строк (сложно представить без уточнения, что есть минимальная строка)?!

В этом случае, если алгоритм является общим для типов, с которыми приходится работать, можно определить шаблон функции. Принцип, в общем случае, будет следующим:

Самым интересным является тот факт, что пока нет вызова функции min, при компиляции она в бинарном коде не создается (не инстанцируется). А если объявить группу вызовов функции с переменными различных типов, то для каждого компилятор создаст свою реализацию на основе шаблона.

Вызов шаблонной функции, в общем, эквивалентен вызову обыкновенной функции. В этом случае компилятор определит, какой тип использовать вместо Type, на основании типа фактических параметров. Но если подставляемые параметры окажутся разных типов, то компилятор не сможет вывести (инстанцировать шаблон) реализацию шаблона. Так, в ниже следующем коде компилятор споткнётся на третьем вызове, так как не может определить, чему равен Type (подумайте, почему?):

Решается эта проблема указанием конкретного типа при вызове функции.

Источник

Шаблоны (справочник по 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.0 можно ссылаться на вложенные свойства или поля в шаблоне свойства. Например, можно выполнить рефакторинг метода из предыдущего примера в следующий эквивалентный код:

Дополнительные сведения см. в разделе Шаблон свойства в примечании к предлагаемой функции и примечание к предлагаемой функции Расширенные шаблоны свойств.

Позиционный шаблон

Начиная с C# 8.0, вы можете использовать позиционный шаблон для деконструкции результата выражения и сравнения результирующих значений с соответствующими вложенными шаблонами, как показано в следующем примере:

В предыдущем примере тип выражения содержит метод Deconstruct, используемый для деконструкции результата выражения. Можно также сопоставлять выражения кортежных типов с позиционными шаблонами. Таким образом можно сопоставить несколько входных значений с различными шаблонами, как показано в следующем примере:

В предыдущем примере используются реляционные и логические шаблоны, доступные в C# 9.0 и более поздних версиях языка.

Можно также расширить позиционный шаблон одним из следующих способов:

Добавьте проверку типа среды выполнения и объявление переменной, как показано в следующем примере:

Используйте шаблон свойства в позиционном шаблоне, как показано в следующем примере:

Можно объединить два предыдущих варианта, как показано в следующем примере:

Позиционный шаблон является рекурсивным шаблоном. Это значит, что любой шаблон можно использовать как вложенный шаблон.

Дополнительные сведения см. в разделе Позиционный шаблон в примечании к предлагаемой функции.

Шаблон var

В шаблоне var тип объявленной переменной равен установленному во время компиляции типу выражения, сопоставляемого с данным шаблоном.

Дополнительные сведения см. в разделе Шаблон var в примечании к предлагаемой функции.

Шаблон пустой переменной

В предыдущем примере шаблон пустой переменной используется для обработки значения null и любых целочисленных значений, которые не соответствуют имеющимся членам перечисления DayOfWeek. Благодаря этому гарантируется, что выражение switch в приведенном примере сможет обработать все возможные входные значения. Если в выражении switch не используется шаблон пустой переменной и при этом ни один из шаблонов выражения не соответствует входным данным, среда выполнения генерирует исключение. Если выражение switch не обрабатывает все возможные входные значения, компилятор генерирует предупреждение.

Дополнительные сведения см. в разделе Шаблон пустой переменной в примечании к предлагаемой функции.

Шаблон в круглых скобках

Начиная с C# 9.0, вы можете использовать круглые скобки вокруг любого шаблона. Как правило, это делается для того, чтобы подчеркнуть или изменить приоритет логических шаблонов, как показано в следующем примере:

Спецификация языка C#

Дополнительные сведения см. в следующих примечаниях к предлагаемой функции.

Источник

Введение в магию шаблонов

Зачем?

79e86379e2074b68b1070ffebb533855
Мы используем шаблоны для красоты. Каждый С++ разработчик знает, что такое красота, красота — это когда код компактный, понятный и быстрый.

Мета-магия и неявные интерфейсы

Что такое метопрограмма? Метопрограмма — это программа, результатом работы которой будет другая программа. Для С++ выполнением метапрограмм занимается компилятор, а результатом является бинарный файл.

d57f93a59876477d9b5ed1b93003a9f3

Именно для написания метапрограмм используются шаблоны.
Чем еще отличается полиморфизм шаблонов от полиморфизма виртуальных функций? Если класс обладает явным интрерфейсом, который мы определили в объявлении класса, то далее в программе объекты этого типа могут использоваться в соответствии с этим самым интерфесом. А вот для шаблонов мы используем неявные интерфейсы, т.е. использованием объекта типа мы определяем неявный интерфейс типа, который выведет компилятор при построении метапрограммы.

Первые заклинания: волшебная дубина

44a9395234514e3aac13b9df40b34bba

Конкретизируем наш шаблон и посмотрим, какие типы мы получили для различных параметров шаблона:

В выводе программы видно, что типы конкретизаций шаблона разные даже для эквивалентных типов — unsigned char & char. При этом они идентичны для char & CHAR, т.к. typedef не создает тип, а лишь дает ему другое имя. Идентичны они и для выражений 1 и 2-1, т.к. компилятор вычисляет выражения и вместо 2-1 использует 1.

Отсюда и вытекает, что мы не можем использовать для шаблонов раздельную компиляцию без дополнительных проблем:

Вообще, в стандарте С++ для этого есть ключевое слово export, однако эта фича слишком труднореализуема и отсутствует в большинстве компиляторов. Есть компиляторы, которые ее поддерживают, но не советую ее использовать в переносимом коде.

Кроме классов существуют и шаблоны функций:

Если компилятор может вывести тип параметра шаблона из типа параметров — он так и поступит, при этом нам не нужно указывать его в коде. Если нет, то мы можем определить разрешающую функцию:

Она не несет никаких накладных расходов.

Специализация — это новый уровень

03fdbb095e7d4de4a1f2c7181c7e9b3a

Обычно используя шаблоны мы хотим написать универсальный код, однако в некоторых случаях мы можем проиграть в производительности. Для решения проблемы существует специальное заклятие — специализация шаблона. Специализация — это повторное определение шаблона с конкретным типом либо классом типов:

Компилятор сам выберет наиболее точно подходящую специализацию, в примере это класс типов “указатель на тип”.

Зловещая магия: рекурсия

Специализации и тот факт, что мы можем использовать шаблоны в шаблонах, дает дам одну очень интересную возможность — рекурсия времени компиляции.

image loader

Самый простой и популярный пример — вычисление какого-либо ряда или полинома, скажем, сумма ряда натуральных чисел:

Смотрим… Работает! Круто? Увеличим количество итераций до 500:

Теперь компиляция занимает больше времени, при этом время выполнения программы — константа! Чудеса!

Не делай козу если хотел грозу

Тут есть пара моментов.

image loader

Максимальная глубина рекурсии по умолчанию ограничена реализацией, для нового gcc это 900, для старых версий он меньше. Параметр

снимает это ограничение.

Второй подводный камень — не ждите отчетов об ошибках. Меняем сумму на факториал:

Получаем некорректный результат, и ни одного предупреждения.

Третий момент, очевидный: мы можем создать слишком много почти одинаковых конкретизаций шаблона и вместо прироста производительности получить прирост бинарного кода.

Мощные заклинания древних

А можно ли совместить магию наследования с шаблонной магией?

image loader

Древние используют для этого заклинание CRTP. Идея проста: применить не виртуальное наследование и обеспечить полиморфное поведение с помощью явного приведения типа наследника к типу родителя. Давайте рассмотрим пример использования:

Мы получаем наследуемые inline методы с полиморфным поведением! Кто скажет что это не круто — мой враг навсегда.

Древние также советуют добавлять в конструктор родителя что-то типа того:

Чтобы демоны, разбуженные мощным заклинанием, не смогли причинить вред вызвавшему их магу.

2b293c42d9eb4d66a8f2f3b976768ff4

Есть еще много тайных техник, древних и не очень. Надеюсь на не скорую встречу /*в аду*/, и да прибудет с вами мощь древних.

Источник

Шаблоны в C++ — часть 2

Эта статья является продолжением первой части про шаблоны и шаблонные функции в C++.

Шаблонные функции-члены

Как видите, всё просто. Шаблонными могут быть не только статические функции. Но необходимо учесть, что виртуальные функции не могут быть шаблонными:

Также шаблонными могут быть операторы и конструкторы:

Чтобы выйти из ситуации, мы определим шаблонные версии конструкторов и операторов.

Как вы могли заметить, я убрал из класса нешаблонные конструктор и оператор сравнения, оставив только их шаблонные версии. Но можно было их и оставить, чтобы лучше организовать работу с одинаковыми типами (например, избежать накладных расходов на преобразования).

Также, заметьте, что в классе остался нешаблонный конструктор копий(#2), т.к. если его убрать, то компилятор сгенерирует его самостоятельно, т.е. шаблонный конструктор(#3) не заменяет конструктор копий(#2) и если не определить копирующий конструктор явно, то компилятор самостоятельно его сгенерирует. То же самое относится и к оператору присваивания. В C++11 это же относится еще и к move-версиям.

Также, стоит отметить, что при разных значениях аргументов шаблона у нас получаются разные типы, значит, my_class не имеет доступа к приватным данным класса my_class (и к защищенным, если не является наследником). Поэтому для обращения к приватным данным необходимо объявить эти классы друзьями (#1). Если убрать это объявление, то получим ошибку вида «m_x is private».

Из данного примера также видно, что у шаблонного класса могут быть шаблонные функции-члены. Хочется еще отметить, что у шаблонного класса могут быть виртуальные функции, но виртуальные функции не могут быть шаблонными.

Аргументы шаблона не-типы.

Аргументами шаблона могут быть не только типы. Рассмотрим небольшой пример

Как известно, при разных аргументах шаблона компилятор создаст разные типы. То есть my_class и my_class — это разные типы. Так же и с параметрами не-типами, т.е. my_type и my_type — это тоже разные типы. Это свойство нам тоже понадобится в дальнейшем. Но на время отвлечемся от этого.

Для начала определимся, что же можно использовать в качестве аргумента не-типа. Для этого обратимся к стандарту.

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

Ух ты, какой выбор. Но увы, есть и другие ограничения.

A template-argument for a non-type, non-template template-parameter shall be one of:

Это еще не всё, но для начала этого достаточно. Попробуем передать в параметр шаблона ссылку на объект:

Для начала этой информации нам хватит.

Шаблонные параметры шаблона.

Кратко рассмотрим передачу шаблона в качестве аргумента шаблона. Создадим функцию, которая принимает в качестве аргумента бинарный предикат и ссылки на две переменные. В случае если предикат возвращает true, делаем swap переданных переменных:

В данном случае использование ключевого слова class — принципиально и typename его не заменит.

template class BinPred — вот он, наш шаблонный аргумент. Как видим, у шаблонного аргумента один аргумент, его имя не имеет значения, поэтому отсутствует. BinPred ()(obj1,obj2) — здесь создаем объект класса BinPred () и вызываем у него функцию operator() с двумя параметрами.

Теперь перепишем функцию main:

Вот мы и сделали еще один крохотный шажок в изучении шаблонов.

Источник

Справочник по обустройству дома и дачи
Adblock
detector