4 что такое параметры шаблона функции

от admin

Лабораторная работа № 6 шаблоны функций и классов

Цель. Получить практические навыки создания шаблонов и использования их в программах С++.

Основное содержание работы.

Создать шаблон заданного класса и использовать его для данных различных типов.

Краткие теоретические сведения.

Шаблон функции.

Шаблон функции (иначе параметризированная функция) определяет общий набор операций (алгоритм), которые будут применяться к данным различных типов. При этом тип данных, над которыми функция должна выполнять операции, передается ей в виде параметра на стадии компиляции.

В С++ параметризированная функция создается с помощью ключевого слова template. Формат шаблона функции:

template <class тип_данных> тип_возвр_значения имя_функции(список_параметров)

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

Имена параметров шаблона должны быть уникальными во всем определении шаблона.

Список параметров шаблона не может быть пустым.

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

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

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

В списке параметров прототипа шаблона имена параметров не обязаны совпадать с именами тех же параметров в определении шаблона.

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

Шаблон класса.

Шаблон класса (иначе параметризованный класс) используется для построения родового класса. Создавая родовой класс, вы создаете целое семейство родственных классов, которые можно применять к любому типу данных. Таким образом, тип данных, которым оперирует класс, указывается в качестве параметра при создании объекта, принадлежащего к этому классу. Подобно тому, как класс определяет правила построения и формат отдельных объектов, шаблон класса определяет способ построения отдельных классов. В определении класса, входящего в шаблон, имя класса является не именем отдельного класса, а параметризованным именем семейства классов.

Общая форма объявления параметризованного класса:

template <class тип_данных> class имя_класса < . . . >;

Основные свойства шаблонов классов.

Компонентные функции параметризованного класса автоматически являются параметризованными. Их не обязательно объявлять как параметризованные с помощью template.

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

Если friend-функция содержит в своем описании параметр типа параметризованного класса, то для каждого созданного по данному шаблону класса имеется собственная friend-функция.

В рамках параметризованного класса нельзя определить friend-шаблоны (дружественные параметризованные классы).

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

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

Локальные классы не могут содержать шаблоны в качестве своих элементов.

Компонентные функции параметризованных классов.

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

Определение должно начинаться с ключевого слова template, за которым следует такой же список_параметров_типов в угловых скобках, какой указан в определении шаблона класса.

За именем_класса, предшествующим операции области видимости (::), должен следовать список_имен_параметров шаблона.

template<список_типов>тип_возвр_значения имя_класса<список_

имен_ параметров> : : имя_функции(список_параметров)

Порядок выполнения работы.

1. Создать шаблон заданного класса. Определить конструкторы, деструктор, перегруженную операцию присваивания (“=”) и операции, заданные в варианте задания.

2. Написать программу тестирования, в которой проверяется использование шаблона для стандартных типов данных.

3. Выполнить тестирование.

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

5. Написать программу тестирования, в которой проверяется использование шаблона для пользовательского типа.

6. Выполнить тестирование.

Методические указания.

1. Класс АТД реализовать как динамический массив. Для этого определение класса должно иметь следующие поля:

 указатель на начало массива;

 максимальный размер массива;

 текущий размер массива.

2. Для вода и вывода определить в классе функции input и print.

3. Чтобы у вас не возникало проблем, аккуратно работайте с константными объектами. Например:

конструктор копирования следует определить так:

MyTmp (const MyTmp& ob);

операцию присваивания перегрузить так:

MyTmp& operator = (const MyTmp& ob);

4. Для шаблонов множеств, списков, стеков и очередей в качестве стандартных типов использовать символьные, целые и вещественные типы. Для пользовательского типа взять класс из лабораторной работы № 1.

5. Для шаблонов массивов в качестве стандартных типов использо-

вать целые и вещественные типы. Для пользовательского типа взять класс “комплексное число” complex.

class complex<

int re; // действительная часть

int im; // мнимая часть

public;

// необходимые функции и перегруженные операции

6. Реализацию шаблона следует разместить вместе с определением в заголовочном файле.

7. Программа создается как EasyWin-приложение в Borland C++5.02.

8. Тестирование должно быть выполнено для всех типов данных и для всех операций.

Содержание отчета.

1. Титульный лист: название дисциплины, номер и наименование работы, фамилия, имя, отчество студента, дата выполнения.

2. Постановка задачи.

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

То же самое следует указать для пользовательского класса.

3. Определение шаблона класса с комментариями.

4. Определение пользовательского класса с комментариями.

5. Реализация конструкторов, деструктора, операции присваивания и операций, которые заданы в варианте задания.

6. То же самое для пользовательского класса.

7. Результаты тестирования. Следует указать для каких типов и какие операции проверены и какие выявлены ошибки (или не выявлены)

Вопросы для самоконтроля.

1. В чем смысл использования шаблонов?

2. Каковы синтаксис/семантика шаблонов функций?

3. Каковы синтаксис/семантика шаблонов классов?

4. Напишите параметризованную функцию сортировки массива методом обмена.

5. Определите шаблон класса “вектор”  одномерный массив.

6. Что такое параметры шаблона функции?

7. Перечислите основные свойства параметров шаблона функции.

8. Как записывать параметр шаблона?

9. Можно ли перегружать параметризованные функции?

10. Перечислите основные свойства параметризованных классов.

11. Может ли быть пустым список параметров шаблона? Объясните.

12. Как вызвать параметризованную функцию без параметров?

13. Все ли компонентные функции параметризованного класса являются параметризованными?

14. Являются ли дружественные функции, описанные в параметризованном классе, параметризованными?

15. Могут ли шаблоны классов содержать виртуальные компонентные функции?

16. Как определяются компонентные функции параметризованных классов вне определения шаблона класса?

Варианты заданий.

1.Класс  одномерный массив. Дополнительно перегрузить следующие операции:

[]  доступ по индексу.

2. Класс  одномерный массив. Дополнительно перегрузить следующие операции:

int()  размер массива;

[]  доступ по индексу.

3. Класс  одномерный массив. Дополнительно перегрузить следующие операции:

[]  доступ по индексу;

= =  проверка на равенство;

!=  проверка на неравенство.

4. Класс  множество set. Дополнительно перегрузить следующие операции:

+  добавить элемент в множество (типа set+item);

5. Класс  множество set. Дополнительно перегрузить следующие операции:

+  добавить элемент в множество (типа item + set);

= =  проверка множеств на равенство.

6. Класс  множество set. Дополнительно перегрузить следующие операции:

—  удалить элемент из множества (типа set-item);

7. Класс  множество set. Дополнительно перегрузить следующие операции:

—  удалить элемент из множества (типа set-item);

>  проверка на подмножество;

!=  проверка множеств на неравенство.

8.Класс  множество set. Дополнительно перегрузить следующие операции:

+  добавить элемент в множество (типа set+item);

int() мощность множества.

9. Класс  множество set. Дополнительно перегрузить следующие операции:

()  конструктор множества (в стиле конструктора для множественного типа в языке Pascal);

10. Класс  множество set. Дополнительно перегрузить следующие операции:

>  проверка на принадлежность (типа операции in множественного типа в языке Pascal);

<  проверка на подмножество.

11. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

+  добавить элемент в начало (list+item);

—  удалить элемент из начала (—list);

= =  проверка на равенство.

12. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

+  добавить элемент в начало (item+list);

—  удалить элемент из начала (—list);

!=  проверка на неравенство.

13. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

+  добавить элемент в конец (list+item);

—  удалить элемент из конца (типа list—);

!=  проверка на неравенство.

14. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

[]  доступ к элементу в заданной позиции, например:

+  объединить два списка;

= =  проверка на равенство.

15. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

[]  доступ к элементу в заданной позиции, например:

+  объединить два списка;

!=  проверка на неравенство.

16. Класс  однонаправленный список list. Дополнительно перегрузить следующие операции:

()  удалить элемент в заданной позиции, например:

()  добавить элемент в заданную позицию, например:

!=  проверка на неравенство.

17. Класс  стек stack. Дополнительно перегрузить следующие операции:

+  добавить элемент в стек;

—  извлечь элемент из стека;

bool()  проверка, пустой ли стек.

18. Класс  очередь queue. Дополнительно перегрузить следующие операции:

bool()  проверка, пустая ли очередь.

19. Класс  одномерный массив. Дополнительно перегрузить следующие операции:

[]  доступ по индексу;

+  сложить элемент с массивом.

20. Класс  одномерный массив. Дополнительно перегрузить следующие операции:

Просто о шаблонах C++

Статья написана с целью максимально просто, на живых примерах рассказать о шаблонах C++.

Как создатели языка пришли к концепции шаблонов? Почему шаблонов не стоит бояться? Как они помогают сделать код чище? Почему стоит изучать шаблоны уже сегодня, несмотря на существующий к ним скепсис?

Статья пытается ответить на все эти и многие другие вопросы.

Вступление

Для того чтобы статья читалась с большей пользой, по желанию можно ознакомиться с несколькими ремарками:

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

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

Стоит компилировать примеры. В идеале, экспериментировать: пробовать менять и улучшать код. Как любая другая тема в программировании, шаблоны лучше всего познаются практикой. Если лень разбираться с настройкой среды разработки , можно использовать какой-нибудь онлайн-компилятор. Например, для анализа ассемблерного кода, получаемого после компиляции, в статье использовался онлайн-компилятор godbolt.org (с отключением оптимизаций опцией «-O0»).

Вопреки традиции учебных материалов, примеры кода не содержат распечатки переменных в поток вывода (без «printf» / «std::cout»). Это было сделано намеренно, чтобы избежать лишнего шума в коде. Если будете компилировать код примеров в IDE, можете просматривать значения переменных в дебаггере. Если же удобнее использовать поток вывода — как вариант, можно использовать следующий макрос:

Цель статьи — рассказать про шаблоны максимально понятно, чтобы пользу от чтения извлёк даже начинающий программист. С позиций бывалого разработчика исходный код примеров далёк от идеала: почти нет проверок на корректность значений переменных, для индексов используется тип «int» вместо «size_t», местами дублируется код, почти не используется передача значений по ссылкам и т.д. Это делалось чтобы минимально уходить в смежные темы, концентируясь, в первую очередь, на иллюстрации использования шаблонов.

Для иллюстрации приёмов на рабочем коде уходить в не связанные с шаблонами темы иногда всё-таки приходилось. Комментарии, не относящиеся напрямую к теме шаблонов, помечены звёздочкой — вот так: (*). В случае, если при прочтении больше интересует тема шаблонов, — такие комментарии можно не читать.

Хабр — преимущественно русскоязычный ресурс. Поэтому я старался писать статью на русском. Как часто бывает в программировании, при этом были трудности с переводом терминов. Например, для понятия «template instantiation» используется несколько «творческий» перевод «порождение шаблона». Неуклюже — однако лучшего перевода придумать не вышло. Чтобы компенсировать возможные непонятки, к определениям терминов привязаны оригинальные названия, которые можно посмотреть наведя мышку . Если вы знаете варианты, которые будут удачнее приведённых в статье, — пишите, обсудим. Я с радостью поменяю терминологию на более распространённую.

Буду благодарен за указание ошибок, опечаток и неточностей в статье. По традиции, в конце заведены титры с перечислением «народных» редакторов. Чтобы комментарии не загромождались лишним спамом, по незначительным замечаниям лучше писать в личку.

Оглавление

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

Концепция шаблонов возникла из принципа программирования Don’t repeat yourself. Можно проследить логику, по которой авторы C++ ввели шаблоны в язык.

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

…переписывают, убирая логику в функцию:

Использование функций даёт несколько преимуществ:

Если надо поменять повторяющуюся логику — достаточно сделать это в функции, не надо менять все копии одинакового кода в программе. Если бы в примере выше вариант без функции содержал системную ошибку в тернарных вызовах, с путаницей порядка операндов: «(a >= b) ? b : a» и «(max_ab >= c) ? c : max_ab» — ошибку пришлось бы искать и править во всех местах использования. Вариант с функцией же требует одной правки — в реализации функции.

При грамотном именовании в коде с функциями логика кода становится прозрачнее. В примере без функции внимательного прочтения требует каждая конструкция вида «(. >= . ) ? . : . » , надо узнавать повторяющуюся логику выбора большего значения из двух каждый раз заново. Функция же во втором варианте именует повторяющуюся логику, за счёт чего общий смысл программы понятнее.

Процедурное программирование делает код чище. Однако, что если логику получения максимального элемента надо поддерживать для всех числовых типов: для всех размеров (1, 2, 4, 8 байт), как знаковых, так и беззнаковых (signed / unsigned), для чисел с плавающей точкой («float», «double»)?

Можно воспользоваться перегрузкой функций:

Выглядит громоздко. Что печальнее, теряются преимущества процедурного программирования. Логика, совпадающая с точностью до типа, копируется в каждой из перегрузок заново.

Придя к тем же неутешительным выводам, в 1985 году разработчики языка придумали шаблоны:

Перегрузки функций с повторяющийся логикой заменились на одну «функцию» с новой конструкцией — template<typename Type>. Слово «функция» взято тут в кавычки намеренно. Это не совсем функция. Данная запись означает для компилятора следующее: «После конструкции template<typename Type> описан шаблон функции, по которому подстановкой типа вместо шаблонного аргумента Type порождаются конкретные функции».

Не стоит путать при этом аргументы функции (в примере — это «Type a» и «Type b») и аргументы шаблона (в примере — это «typename Type»). Первые задают значения, которые принимает функция при вызове. Вторые же задают параметры, подстановкой в которые значений по месту использования порождаются конкретные функции из шаблонов.

Использование шаблона выглядит так: «max<int>(a, b)«. В треугольных скобках передаются значения шаблонных аргументов. В данном случае, в качестве значения шаблонного аргумента «Type» передаётся значение — тип «int». После подстановки компилятор создаст «под капотом» конкретную функцию из обобщённого кода. То, что вызывается по записи «max<int>()», для компилятора выглядит так:

Встречая дальше обращения к шаблонной функции с подстановкой в качестве «Type» типа «int», компилятор будет использовать эту же сгенерированную из шаблона функцию.

Встретив же следующую запись — «max<char>(aChar, bChar)» — компилятор породит для себя новую функцию — но по тому же шаблону:

Несмотря на родство по шаблону, функции «max<int>()» и «max<char>()» — совершенно самостоятельны, каждая из них будет превращаться при компиляции в свой ассемблерный код.

Зафиксируем также терминологию более высокого уровня.

2. Выведение типов шаблонных аргументов

В примере с шаблоном функции «template<Type> max(Type, Type)» использовалась явная передача типов в шаблон. Однако во многих случаях компилятор может автоматически вывести тип шаблонного аргумента.

Вызов шаблонной функции из примера.

. можно записать, опустив <int>:

Такая запись корректна с точки зрения языка. Компилятор проанализирует типы переменных «a» и «b» и выполнит выведение типа для передачи в качестве значения шаблонного аргумента «Type».

Тип переменной «a» — «int», тип переменной «b» – тоже «int». Они передаются в шаблон функции «template<Type> Type max(Type, Type)», в котором ожидается, что оба аргумента будут иметь одинаковый тип «Type». Так как типы «a» и «b» совпадают, и нет других правил ограничивающих данный шаблонный аргумент «Type», компилятор делает вывод, что записью «max(a, b)» ожидают применения шаблонной функции «max<int>(a, b)».

Стоит отметить, что, например, следующий код.

. не скомпилируется с ошибкой вроде: «deduced conflicting types for parameter ‘Type’».

Проблема в том, что для этого кода типы переменных «a» и «b» не совпадают. Компилятор не может однозначно определить какой тип надо передать в качестве значения аргумента «Type». У него есть вариант подставить тип «int» или тип «char». Непонятно какая из подстановок ожидается программистом.

Чтобы избавиться от этой проблемы, можно применить явную передачу типа в шаблон:

Теперь всё хорошо. Шаблонная функция определена однозначно: «int max<int>(int, int)». Значение переменной «bChar» в этом вызове приведётся к типу «int» — так же, как это произошло бы при вызове нешаблонной функции «int max(int, int)» из самого начала статьи.

3. Шаблоны классов

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

Вот, например, описание шаблонного класса Interval. С его помощью можно описывать промежутки значений произвольного типа:

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

Пример использование шаблона класса «template<Type> class Interval»:

Встретив запись «Interval<int>» в первый раз, по шаблону класса будет порождён новый шаблонный класс. Порождённый класс будет выглядеть для компилятора следующим образом:

Так же, как это было с функциями, порождение шаблонного класса выполнится подстановкой «int» вместо «Type». Порождённый тип будет использоваться везде, где шаблон «template<Type> class Interval<Type>» с подстановкой «int».

4. Специализации

Это, пожалуй, один из самых важных и сложных разделов статьи, поэтому он будет длиннее других.

Лучший пример на котором можно разобраться со специализациями шаблонов — шаблон класса «массив». Вспомним, массив – структура данных, хранящая набор однотипных значений последовательно одно за другим в памяти. В стандартной библиотеке шаблонов эту структуру данных реализует шаблон класса «std::vector<>».

Вот элементарная реализация шаблона массива:

По реализации, надеюсь, всё понятно. Рассмотрим пример использования:

«SimpleArray<int>» — шаблонный класс, для получения которого в шаблон «template<Type> class SimpleArray» в качестве аргумента «Type» передаётся тип «int». Массив заполняется с помощью обращения к методу «setElement()», после чего в цикле рассчитывается сумма всех элементов.

Это рабочий шаблон. Однако есть ситуация, в которой он не достаточно эффективен. Вот пример использования шаблонного класса с подстановкой типа bool:

Элементы массива имеют булевый тип, который выражается одним из всего двух возможных значений: «false» или «true» (численно описывающихся, соответственно, значениями «0» или «1»). Вот как «SimpleArray<bool>» использует память для хранения элементов (тут исходим из того, что тип «bool» занимает один байт):

В каждом элементе всего один бит полезной информации. Если бы значения хранились побитно, их можно было бы расположить в восемь раз компактнее.

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

Вот по какому принципу описывается специализация:

Ниже — полный код специализации шаблона класса «template<Type> class SimpleArray».

(*) Ликбез по побитным операциям

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

Операция побитового сдвига влево (<<)

Операция побитового «И» (&)

Операция побитового «ИЛИ» (|)

Операция побитового отрицания (

Разберём принцип доступа к битам на примере. Пусть есть значение длиной в байт, содержащее следующие биты:

Как получить значение бита с заданым индексом? Значение бита может быть либо «0», либо «1», поэтому для его выражения используют тип «bool». «bool» имеет смысл «ложь» если все его биты равны «0» и смысл «истина» если хотя бы один его бит не равен «0». Таким образом, чтобы понять имеет ли интересующий нас бит значение «0» или «1», надо добиться того чтобы все биты кроме интересующего нас приняли значение «0». Для этого используются так называемые битовые маски — значения которыми «фильтруются» интересующие нас биты.

Например, надо получить значение бита с индексом «4». Для того чтобы «обнулить» значения всех битов кроме интересующего, формируется битвая маска в которой бит по индексу «4» имеет значение «1», а все остальные биты — значение «0». После этого, выполнив побитовое «И» каждого бита значения с битами маски можно добиться того чтобы все биты кроме интересующего гарантированно стали равны «0»:

Получение бита 4

Получение бита 0

Разберём обобщённый алгоритм. Как понятно из примеров, чтобы получить значение бита по индексу «bitIndex», надо выполнить операцию побитового «И» между значением и маской, в которой бит по индексу «bitIndex» имеет значение «1», а остальные биты — значение «0». В коде эта логика записывается следующим образом:

Как читать биты терерь известно.

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

Значение нужного бита в байте «сбрасывается» в «0». Этого добиваются выполняя логическое «И» между изменяемым байтом и маской в которой бит по целевому индексу имеет значение «0», а все остальные биты — значение «1».

Сброшенное в «0» значение нужного бита «записываются» нужным значением. Это достигается выполнением логического «ИЛИ» между результатом первого этапа и маской в которой по целевому индексу находится значение «1», а все остальные биты имеют значение «0».

Звучит сложно. Чтобы понять как это работает проще всего будет рассмотреть несколько примеров (в скобках записывается с какого на какое значение бита происходит изменение):

Заполнение бита 2 значением 1 (0 -> 1)

В коде эта логика записывается следующим образом (конкретные значения взяты из первого примера с объяснением выставления полей):

Рассмотрим новый пример использования «template<Type> class SimpleArray» с поддержкой специализации по типу «bool»:

Заканчивая раздел, чуть заглянем в будущее. Изначально специализации создавались как механизм для выбора оптимальной реализации шаблона для конкретного типа. Однако позже они начали исполнять одну из ключевых ролей в метапрограммировании на C++. Возможность выбирать вариации шаблона по передаваемому типу позволяет анализировать типы в программе, что открывает доступ к рефлексии времени компиляции.

5. Валидация шаблонных аргументов

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

Что ж. Добавим немного дёгтя.

Уже при описании шаблонной функции «template<Type> max(Type, Type)» неминуемо возникал вопрос: как проверяется корректность типа, который подставляется в шаблон? Ведь в шаблоне тип как-то используется. Например, что будет если передать в качестве аргумента «template<Type> max(Type, Type)» тип, не поддерживающий оператор «> cpp»>template<typename Type> Type max(Type a, Type b) < return (a >= b ? a : b); > // Структура, определяющая позицию точки в двухмерном пространстве. Для точки // нельзя сказать «больше» ли она другой точки. Можно сравнивать конкретные // координаты («x» или «y») точек, но нельзя сравнить сами точки. Для структуры // Point2D _не определена_ операция сравнения «> Point2D» в аргумент «Type» шаблона // «template<Type> max(Type, Type)» породится шаблонная функция, которая для // компилятора выглядит так: // // Point2D max<Point2D>(Point2D a, Point2D b) // < // return (a >= b ? a : b); // > // // В теле функции выполняется сравнение двух значений («a» и «b») имеющих тип // «Point2D». Однако, как было отмечено выше, для их типа «Point2D» операция // сравнения _не определена_ . Компилятору остаётся лишь сгенерировать ошибку // компиляции вроде следующей (так отображает ошибку компилятор GCC): // // «no match for ‘operator<=’ (operand types are ‘Point2D’ and ‘Point2D’)» return 0; >

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

Сейчас в промышленно используемом C++ нет механизма валидации шаблонных аргументов.

Долгое время это была одна из главных проблем шаблонов и, в целом, одной из главных проблем языка C++. Особенно ужасно она проявляла себя в сложных шаблонных конструкциях из сторонних библиотек. Там ошибки компиляции могли появляться в глубинах логики чужих шаблонов. Приходилось долго разбираться в реализации стороннего кода. Имевшие дело со стандартной библиотекой шаблонов , с её самыми популярными шаблонами классов «std::vector<>» и «std::map<>», наверняка не раз страдали от многоэтажных ошибок компиляции в недрах их реализаций.

Проблему с валидацией решали по-разному. Использовали свойства подстановок, вводили в язык конструкцию «static_assert()», придумывали стили комментариев, в которых текстом описывались бы требования к аргументам шаблонов.

Лишь спустя годы поисков, к версии C++20 комитет по стандартизации языка прекратил хождение по мукам и наконец-то качественно решил вопрос, введя в язык КОНЦЕПТЫ .

Концепты позволяют описывать требования к типу, который передаётся как шаблонный аргумент. Например, для шаблона функции «template<Type> Type max(Type, Type)» с помощью концептов можно потребовать передавать в качестве значения «Type» тип, поддерживающий операцию сравнения. С помощью концептов компилятор может обнаружить ошибку до выполнения некорректной подстановки типа в шаблон.

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

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

На этом закончу пропаганду. Цель статьи — дать вводную начинающим разработчикам, которым предстоит работать с реальным кодом, использующимся в индустрии прямо сейчас. Код этот, увы, написан, в основном, с использованием старых стандартов. Освоим для начала их.

6. Больше шаблонных аргументов

До этого речь шла о шаблонах зависящих от одного аргумента. Но C++ позволяет задавать и большее их количество. Чтобы прочувствовать как это используется в реальном коде, рассмотрим шаблон, зависящий от двух шаблонных аргументов.

Ниже представлена элементарная реализация словаря. Использование шаблона позволяет как ключ, так и значение задавать произвольным типом:

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

К сожалению, тема шаблонов от произвольного количества аргументов слишком обширная. В данной статье мы её касаться не будем. Если когда-нибудь напишу материал по теме — обязательно оставлю здесь ссылку на него.

7. Шаблонные аргументы-константы

До этого рассматривались шаблоны, принимающие лишь типы в качестве шаблонных аргументов. Однако в качестве аргументов шаблонов могут выступать также константы времени компиляции. Такие аргументы по-английски называются non-type template arguments, дословно «шаблонные аргументы не являющиеся типами». Дословный перевод по-русски звучит неуклюже, поэтому дальше будем использовать термин «шаблонные аргументы-константы «.

Рассмотрим синтаксис использования таких аргументов на небольшом примере, который, несмотря на слегка безумную реализацию, демонстирует сразу несколько аспектов использования шаблонных аргументов-констант. Реализуем шаблон функции для расчёта факториала на шаблонных аргументах:

За счёт того, что значение шаблонного аргумента-константы по определению не зависит от вычислений этапа исполнения программы, компилятор с большой вероятностью сможет оптимизировать код при компиляции, подставив в ассемблерном коде константу 4*3*2*1 (то есть, сразу значение 24), вместо полноценного вызова функции «getFactorial<4>()» и всей содержащейся в ней логики.

Рассмотрим какие ещё варианты передачи значения шаблонного аргумента-константы допустимы:

В разделе «Частичные специализации шаблонов» будет ещё один пример, использующий шаблонные аргументы-константы. Он ближе к реальной жизни.

8. Передача шаблонных аргументов в шаблонном контексте

Вероятно, в разделе про шаблонные классы у читающего мог возникнуть резонный вопрос: можно ли передать шаблонный класс в функцию, сохранив код обобщённым? Например, возможно ли описать функцию для получениея максимального элемента в шаблонном массиве «template<Type> SimpleArray».

Читать:
Приложение who is что это такое

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

Такая запись свела на нет все преимущества обобщённого программирования — снова копируется одна и та же логка. Думаю, внимательный читатель без труда вспомнит: статья начиналась с рассмотрения похожей проблемы. Только там копировалась с точностью до типа логика нешаблонных функций «max()», когда понадобилась поддержка всех числовых типов.

Что ж, C++ позволяет использовать шаблон и в такой ситуации. На самом деле, случаи нужного нам типа подстановок встречались в статье раньше, просто внимание на них не акцентировалось. Вот, к примеру, метод шаблона «template<Type> class Interval»:

Вместо повторяющихся перегрузок «getMaxElement()», можно описать шаблон функции, аргумент которой передаётся в шаблон класса «template<Type> class SimpleArray»:

Использованный в примере выше вариант автоматического вывода типов на деле могущественнее, чем кажется. Это один из столпов метапрограммирования. На пару со специализациями, с его помощью можно анализировать типы (как шаблонные, так и нет) которыми оперирует программа. Фактически, вместе эти два механизма позволяют реализовать рефлексию на этапе компиляции.

9. Частичные специализации шаблонов

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

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

В шаблоне класса «template<Type> SimpleArray» использовалась динамическая память:

Использование динамической памяти позволяло создавать массивы разной длины, определяемой на этапе исполнения программы:

Память для элементов выделяется единожды, при создании экземпляров. После этого расширить или сократить объём памяти нельзя. Так ли важна эта возможность?

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

Иллюстрация принципа работы стека и динамической памяти

Картинка, иллюстрирующая принцип работы кучи и стека. Цветные элементы со знаками «+» и «-» иллюстрируют принцип по которым работает, соответственно, выделение и освобождение памяти этих типов.

Блоки памяти в стеке выделяются простым сдвигом вершины стека.

Блоки динамической памяти выделяются и удаляются в произвольных местах динамической памяти, что требует дополнительного управляющего механизма ответственного за поиск свободных блоков — аллокатора.

При этом указатели, которые используются для доступа к блокам динамической памяти, хранятся на стеке.

Можно сравнить ассемблерный код который получится при компиляции примера:

Уже по количеству команд для записи значений элементов видно что использование динамической памяти требует большего количества действий. Однако «call» вызовы для создания и освобождения динамической памяти — это ещё более тяжёлые операции обращения к функциям.

Было бы здорово получить структуру данных, хранящую элементы в стековой памяти. В стандартной библиотеке шаблонов такую структуру реализует шаблон «std::array<>».

Чтобы подобную структуру данных получить из «template<Type> SimpleArray», надо сменить тип поля для хранения элементов массива:

Чтобы это работало, количество элементов массива (значение «Size») надо передавать константой времени компиляции. Такой мехнизм уже известен: константы времени компиляции передаются в шаблоны с помощью шаблонных аргументов-констант. Добавим шаблонный аргумент-константу:

Вот полная реализация обобщённого шаблона класса. Она очень простая:

Теперь внимательному читателю, вероятно, интересно: что же будет со специализацией по типу «bool»? Она, с одной стороны, требует «фиксации» значения первого шаблонного аргумента «Type», с другой — должна поддерживать произвольное значение второго аргумента «Size» (массив флагов может быть любой длины).

Для решения этого вопроса существуют частичные специализации шаблонов :

Рассмотрим пример использования, аналогичный примеру из четвёртого раздела, разобрав логику по которой компилятор будет выбирать специализацию:

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

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

Это ограничение можно обойти, однако, лучше рассмотреть этот вопрос в следующих статьях.

Заключение

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

Если материал окажется не безнадёжно провальным, я планирую написать ещё две статьи по шаблонам. Одна коснётся более сложных тем связанных с шаблонами. Вторая рассмотрит техники и трюки, выступающие примитивами в «большом» метапрограммировании.

Updated: Если вас заинтересовала тема шаблонов в C++, уже сейчас есть хорошая статья от @4eyes, рассматривающая более продвинутые техники метапрограммирования на практическом примере: «О шаблонах чуть сложнее».

1. Вопрос: Чем шаблоны отличаются от макросов?

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

Эти вопросы логично возникают у программистов, изучающих шаблоны. Действительно, на первый взгляд, шаблоны похожи на макроподстановки компиляторов. Оба механизма позволяют «генерировать» код по определённому трафарету с подстановкой передаваемых по месту применения значений-аргументов.

Главная разница заключается в том, что макросы — это действия с текстом, который не воспринимается компилятором как исходный код состоящий из определения функций, переменных, выражений, и т.д. С текстом программы работает препроцессор, для которого программа — набор символов (букв, цифр, знаков для операторов, пробелов, и т.д.), которые можно копировать и вставлять полностью аналогично тому как программист это делает в IDE с помощью Ctrl+C, Ctrl+V:

#include — указание «вставить вместо макроса весь текст содержащийся в файле»

#define — указание «встречая идентификатор определяющий макрос, вставить текст следующий за макросом с заменой аргументов передаваемым по месту использования текстом».

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

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

Допустим, имеется следующие фукнции для работы с файлами, содержащими числа:

Задача следующая — надо найти максимальное число в файле.

Для начала рассмотрим как в этой задаче сработает обобщённая логика для поиска максимального значения, описанная с помощью макроса:

На первый взгляд, логика должна работать корректно.

Однако давайте посмотрим в какой код буквально раскроется строчка с макросом:

Именно с таким кодом функция «main()» отправится на компиляцию. Если обратить внимание на то как работает функция «loadNextValueFromFile()» и внимательно вчитаться в то что сгенерировал препроцессор, в программе можно увидеть неприятный баг.

Вот как выполнится логика алгоритма если в файле содержатся числа «1, 3, 0»:

Записываем в currentMax первое число из файла (число «1»).

Для расчёта значения по условию «false» снова вызывается «loadNextValueFromFile(«file»)». Этот вызов вернёт число «0», так как каретка передвинулась при вычислении сравнения. В currentMax записывается число «0», которое, очевидно, не является самым большим в файле.

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

Из-за примитивности механизма работы препроцессора, использование макросов в прикладном коде будет стабильно приводить к подобным трудно уловимым ошибкам.

Поэтому лучше предпочитать макросам шаблоны:

«max<int>()» — это простой вызов функции. При вызове функций выражения, передаваемые в качестве аргументов, вычисляются единожды перед передачей в функцию. Ошибки компиляции внутри шаблонной функции будут проверяться компилятором на уровне логических конструкций программы, без «магического» собирания текста программы из кусков.

2. Вопрос: Увеличивают ли шаблоны объём скомпилированного кода?

Ответ: Относительно эквивалентной логики без шаблонов — нет.

Уже отмечалось, что для разных шаблонных функций и классов порождаются полностью самостоятельные фрагменты кода. Если есть шаблон функции от одного аргумента который используется с подстановкой трёх разных типов — объём полученного в результате компиляции кода будет в три раза больше, чем требующийся для одной.

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

Сравнение ассемблерного кода для функции «max()»

Код без шаблонов:

Код с шаблонами:

Сравнение ассемблерного кода:

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

Сравнение ассемблерного кода для метода «Interval::intersection()»

Код с шаблонами

Сравнение ассемблерного кода:

3. Вопрос: Увеличивают ли шаблоны расход ресурсов на компиляцию кода?

Ответ: Да, но после перехода на C++20 ситуация может стать лучше.

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

Концепты из нового стандарта C++20 могут поменять ситуацию — по крайней мере, со временем выполнения компиляции. Они позволяют останавливать подстановку аргументов в шаблон до полноценного формирования шаблонного типа, и экономить таким образом время на завершение генерации априори некорректного типа.

4. Вопрос: Затрудняют ли шаблоны отладку кода?

Ответ: Поиск ошибок компиляции — затрудняют (однако с приходом концептов из C++20 станет лучше). Отладку ошибок в логике исполнения программы — нет.

Проблемы разбора ошибок компиляции были подробно описаны в пятом разделе.

Name already in use

CPP-2019 / texts / 03-FunTemplates.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Одной из популярных современных парадигм программирования на C++ является обобщенное программирование, связанное с параметризацией типов. В любой, достаточно крупной задаче, неизбежно дублирование кода, реализующего некоторые алгоритмы и структуры данных. Дублирование возникает из-за разных типов обрабатываемых данных. То есть алгоритм один, а все отличие — в типах. Чтобы избежать дублирования используют шаблоны, с параметрами-типами.

В С++ существую шаблоны функций, шаблоны классов и шаблоны переменных. Далее, в первую очередь рассматриваются шаблоны функций.

Шаблон функций позволяет задать описание функции без привязки к конретным типам. В место них применяют типы-параметры.

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

Вызов шаблонной функции подразумевает автоматическое определение типа параметра:

В момент обработки вызова, компилятор пытается инстанцировать функцию из шаблона с выведением типа Т. Нетрудно видеть, что данный вызов предполагает использование типа int.

Наиболее известный пример: функция обмена значений двух переменных

Для получения функции проведем инстанцирование:

Передача ссылки на массив

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

Рассмотрим случай, когда ссылки нет:

В качестве ответа, выводимого на экран мы увидим Pi, то есть указатель на int. Так происходит потому что при передаче имени массива без ссылочного параметра в функции мы получим указатель на первый элемент.

Рассмотрим случай с ссылкой:

Теперь ответ другой: Ai3, то есть массив из трех целых.

Пример шаблона с целочисленным параметром

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

Пример шаблона с параметров в виде шаблона

В следующем примере функция принимает стандартный контейнер из библиотеки STL и выводит его в выходной поток. Обратите внимание, что у шаблонного класса контейнера есть второй параметр — аллокатор.

Передача функции в шаблонную функцию

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

Вывод типов параметров шаблонов

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

Случай 1. lvalue-ссылка

Ссылочная часть игнорируется

При добавление const в параметр:

Случай 2. rvalue-ссылка

В этом случае надо смотреть, к какому типу выражений (lvalue или rvalue) принадлежит фактический параметр.

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

ищется набор специализаций шаблонов функций, которые примут участие в разрешении перегрузки. Это осуществляется с учетом всех шаблонов функции. Принимается решение, какие аргументы функции были бы использованы (если вообще были бы), если бы в текущей области видимости не было других шаблонов функций и обычных функций с тем же именем. В примере с вызовом sqrt(z) к рассмотрению будут приняты sqrt(complex) и sqrt<complex >(complex ).

если могут быть вызваны два шаблона функции, и один из них более специализирован, чем другой, на следующих этапах только он и рассматривается. В примере с вызовом sqrt(z), это означает, что предпочтение отдается sqrt(complex ) по отношению к sqrt<complex>(complex ): любой вызов, который соответствует sqrt(complex T), так же соответствует и sqrt(T).

разрешается перегрузка для этого набора функций, а также для любых обычных функций (для них перегрузка разрешается как для обычных функций). Если аргументы функции шаблона были определены путем выведения по фактическим аргументам шаблона, к ним нельзя применять «продвижение», стандартные и определяемые пользователем преобразования. Для sqrt(2) sqrt(int) является точным соответствием и поэтому ей отдается предпочтение по отношению к sqrt(double).

если и обычная функция и специализация подходят одинаково хорошо, отдается предпочтение обычной функции. Следовательно, для sqrt(2.0) выбирается sqrt(double), а не sqrt(double).

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

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

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

Еще пример специализации:

Шаблоны и рекурсия

Необычными свойствами обладает использование рекурсии при работе с шаблонами

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

Кстати, при попытке выбрать большое значение для func выдается следующая ошибка:

Для обхода этого ограничения служит специальная опция компилятора:

Еще пример использования рекурсии в шаблонах:

Возведение числа в степень:

Пример для определения максимального числа:

В стандарте С++11 определены вариативные шаблоны с переменным количеством параметров.

Они дают возможность создавать функции с переменным числом аргументов.

Специализация шаблонов и перегрузка

Необходимо определить, какие версии шаблона будет использовать компилятор.

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

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

То есть в примере выше (2) является специализацией не (3) а (1), поэтому для перегрузки будет выбран более подходящий базовый шаблон (3). Во втором же случае (6) является специализацией (5), которая в свою очередь является лучшей кандидатурой, чем (4).

Упражнение

Переставьте местами описания функций 2 и 3 и проверьте результат. Попробуйте также изменить порядок следования функций 4-6.

Эта возможность появилась в С++14. Ее применение иллюстрирует следующий пример:

Параметры шаблонов и аргументы шаблонов

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

template < parameter-list > declaration

Каждый параметр в списке параметров может быть:

  • параметр шаблона нестандартного типа;
  • параметр шаблона типа;
  • параметр шаблона.
Не тип шаблона параметр
typename(optional) (1)
typename(optional) = default (2)
type . name(optional) (3) (since C++11)
placeholdername (4) (since C++17)

Параметр шаблона,не относящийся к типу,должен иметь structural type , который является одним из следующих типов (необязательно cv-qualifiers, квалификаторы игнорируются):

  • lvalue ссылочный тип (для объекта или для функции);
  • интегральный тип ;
  • тип указателя (для объекта или функции);
  • указатель типа элемента (к объекту члена или к функции члена);
  • тип перечисления ;
  • тип с плавающей запятой ;
  • литеральный тип класса со следующими свойствами:
    • все базовые классы и нестатические члены данных являются общедоступными и неизменяемыми,и
    • типы всех базовых классов и нестатических членов данных являются структурными типами или их (возможно,многомерными)массивами.

    Типы массивов и функций могут быть записаны в шаблонное объявление,но они автоматически заменяются указателем на объект и указателем на функцию соответствующим образом.

    Когда имя параметра шаблона нетипичного типа используется в выражении в теле шаблона класса, это неизменяемое значение типа prvalue, если только его тип не является ссылочным типом lvalue или если его тип не является типом класса (поскольку C ++ 20).

    Параметр шаблона class Foo формы Foo не является неназванным параметрическим параметром типа Foo , даже если в противном случае class Foo представляет собой разработанный спецификатор типа, а class Foo x; объявляет, что x имеет тип Foo .

    Тип нетипичного параметра шаблона может быть выведен, если он включает тип заполнителя ( auto , заполнитель для выведенного типа класса (начиная с C ++ 20) или decltype(auto) ). Вывод выполняется так, как будто путем определения типа переменной x в изобретенном объявлении T x = template-argument; где T — объявленный тип параметра шаблона. Если выведенный тип не разрешен для нетипичного параметра шаблона, программа некорректна.

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

    Идентификатор , который называет нетиповой параметр шаблона типа класса T , обозначает статический объект длительности хранения типа const T , называемый объект шаблонного параметра , значение которого равно значению соответствующего аргумента шаблона после его преобразования в тип параметра шаблона. Все такие параметры шаблона в программе одного типа с одинаковым значением обозначают один и тот же объект параметра шаблона. Объект параметра шаблона должен иметь постоянное уничтожение .

    Тип шаблона параметр
    type-parameter-keyname(optional) (1)
    type-parameter-keyname(optional) = default (2)
    type-parameter-key . name(optional) (3) (since C++11)
    type-constraintname(optional) (4) (since C++20)
    type-constraintname(optional) = default (5) (since C++20)
    type-constraint . name(optional) (6) (since C++20)
    type-parameter-key либо typename , либо class . Между этими ключевыми словами в объявлении параметра шаблона типа нет никакой разницы.
    type-constraint либо имя концепции, либо имя концепции, за которой следует список аргументов шаблона (в угловых скобках). В любом случае, имя концепции может быть необязательно

    Имя параметра является необязательным:

    В теле декларации шаблона имя параметра типа-это имя типа,которое является псевдонимом типа,поставляемого при инстанциировании шаблона.

    Каждый ограниченный параметр P , тип-ограничение которого равен Q, обозначающий концепцию C , вводит выражение-ограничение E согласно следующим правилам:

    • если Q является C (без списка аргументов),
      • если P не является пакетом параметров, E просто C<P>
      • в противном случае P является пакетом параметров, E является выражением сгиба (C<P> && . )
      Параметр шаблона шаблона
      template < parameter-list > type-parameter-keyname(optional) (1)
      template < parameter-list > type-parameter-keyname(optional) = default (2)
      template < parameter-list > type-parameter-key . name(optional) (3) (since C++11)
      type-parameter-key class или typename (начиная с C++17)

      В теле декларации шаблона имя этого параметра является именем шаблона (и требует инстанцирования аргументов).

      Разрешение имен для параметров шаблона

      Имя параметра шаблона не может быть повторно объявлено в его области видимости (включая вложенные области видимости).Параметр шаблона не может иметь то же имя,что и имя шаблона.

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

      В определении члена шаблона класса,который появляется вне пространства имен,содержащего определение шаблона класса,имя параметра шаблона скрывает имя члена этого пространства имен.

      В определении шаблона класса или в определении члена такого шаблона, который появляется вне определения шаблона, для каждого независимого базового класса, если имя базового класса или имя члена базового class совпадает с именем параметра шаблона, имя базового класса или имя члена скрывает имя параметра шаблона.

      Template arguments

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

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

      Шаблонные нетипичные аргументы

      Следующие ограничения применяются при инстанцировании шаблонов,которые имеют нетипные параметры шаблона:

      • Для целочисленных и арифметических типов аргумент шаблона, предоставляемый во время создания экземпляра, должен быть преобразованным константным выражением типа параметра шаблона (поэтому применяется определенное неявное преобразование).
      • Для указателей на объекты аргументы шаблона должны обозначать адрес полного объекта со статической продолжительностью хранения и связью (внутренней или внешней) или постоянное выражение, которое возвращает соответствующий нулевой указатель или std::nullptr_t (поскольку C ++11) значение.
      • Для указателей на функции допустимыми аргументами являются указатели на функции с линковкой (или константные выражения,которые оценивают до нуля значения указателя).
      • Для ссылочных параметров lvalue аргумент,приводимый при инстанциировании,не может быть временным,безымянным значением lvalue или именованным значением lvalue без связи (другими словами,аргумент должен иметь связь).
      • Для указателей на члены аргумент должен быть указателем на член, выраженным как &Class::Member , или постоянным выражением, которое оценивается как нулевой указатель или std::nullptr_t (начиная с C++11).

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

      Аргумент шаблона, который можно использовать с нетиповым параметром шаблона, может быть любым преобразованным константным выражением типа параметра шаблона.

      Единственным исключением является то,что нетипные параметры шаблонов для reference or pointer тип и нестатические члены данных ссылочного или указательного типа в нетипном шаблоне параметра типа класса и его подобъектов (начиная с C++20)не могут ссылаться/быть адресом.

      • временный объект (в том числе созданный во время инициализации ссылки );
      • строковый литерал ;
      • результат typeid ;
      • предопределенная переменная __func__ ;
      • или подобъект (включая нестатический член класса,базовый подобъект или элемент массива)одного из вышеперечисленных объектов (начиная с C++20).
      Аргументы типа шаблона

      Аргумент шаблона для параметра шаблона типа должен быть идентификатором типа , который может называть неполный тип:

      Аргументы шаблона шаблона

      Аргумент шаблона для параметра шаблона шаблона должен быть id-выражением , которое называет шаблон класса или псевдоним шаблона.

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

      Чтобы сопоставить аргумент шаблона шаблона A с параметром шаблона шаблона P , P должен быть по крайней мере,столь же специализированные как A (см. ниже). Если список параметров P включаетпакет параметров , ноль или более параметров шаблона (или пакетов параметров) из списка параметров шаблона A соответствуют ему (начиная с C++11).

      Формально шаблон шаблона-параметра P равен по крайней мере,столь же специализированные в качестве аргумента шаблона шаблона A , если при следующей перезаписи двух шаблонов функций шаблон функции, соответствующий P , является по крайней мере столь же специализированным, как шаблон функции, соответствующий A , в соответствии с правилами частичного упорядочения для шаблонов функций . Учитывая изобретенный шаблон класса X со списком параметров шаблона A (включая аргументы по умолчанию):

      • Каждый из двух шаблонов функций имеет те же параметры шаблона, что и P или A соответственно .
      • Каждый шаблон функции имеет один параметр функции, тип которого является специализацией X с аргументами шаблона, соответствующими параметрам шаблона из соответствующего шаблона функции, где для каждого параметра шаблона PP в списке параметров шаблона шаблона функции соответствующий аргумент шаблона AA сформирован. Если PP объявляет пакет параметров, то AA является расширением пакета PP. ; в противном случае (начиная с C++11) AA является id-выражением PP .

      Если переписывают производит неверный тип, то P не , по крайней мере специализированы , как A .

      Перед принятием P0522R0 каждый из параметров шаблона A должен точно соответствовать соответствующим параметрам шаблона P .Это препятствует принятию многих разумных шаблонных аргументов.

      Хотя на это было указано очень рано ( CWG # 150 ), к тому времени, когда оно было разрешено, изменения были применены к рабочему документу C++ 17, и решение стало де-факто функцией C++ 17. Многие компиляторы отключают его по умолчанию:

      • GCC отключает его во всех языковых режимах до C++17 по умолчанию, его можно включить, только установив флаг компилятора в этих режимах.
      • Clang по умолчанию отключает его во всех языковых режимах, его можно включить, только установив флаг компилятора.
      • Microsoft Visual Studio рассматривает его как обычную функцию C++17 и включает только в языковых режимах C++17 и более поздних версиях (т. е. не поддерживает языковой режим C++14, который является режимом по умолчанию).
      Шаблонные аргументы по умолчанию

      Аргументы шаблона по умолчанию указываются в списках параметров после знака = .Значения по умолчанию можно указать для любого типа параметра шаблона (типа, нетипа или шаблона), но не для пакетов параметров (начиная с C++11).

      Если для параметра шаблона первичного шаблона класса,первичного шаблона переменной,(с C++14)или шаблона псевдонима указано значение по умолчанию,каждый последующий параметр шаблона должен иметь аргумент по умолчанию,за исключением самого последнего,который может быть пакетом параметров шаблона (с C++11).В шаблоне функции нет ограничений на параметры,которые следуют за параметром по умолчанию,а за пакетом параметров могут следовать более типовые параметры,только если они имеют значения по умолчанию или могут быть выведены из аргументов функции (с C++11).

      Параметры по умолчанию не допускаются.

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

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

      Аргументы шаблона по умолчанию,которые появляются в объявлениях,объединяются аналогично аргументам функции по умолчанию:

      Но один и тот же параметр не может быть дважды задан аргументами по умолчанию в одной и той же области видимости:

      При синтаксическом анализе аргумента шаблона по умолчанию для параметра шаблона, не являющегося типом, первый невложенный > принимается за конец списка параметров шаблона, а не за оператор «больше чем»:

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

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

      Аргумент шаблона по умолчанию неявно инстанцируется,когда необходимо значение этого аргумента по умолчанию,за исключением случаев,когда шаблон используется для имени функции:

      Эквивалентность аргументов шаблона

      Эквивалентность аргументов шаблона используется для определения того , совпадают ли два идентификатора шаблона .

      Два значения template-argument-equivalent если они одного типа и.

      • они имеют тип интеграла или перечисления,и их значения одинаковы
      • или они имеют тип указателя и имеют одинаковое значение указателя
      • или они имеют тип указателя на член и ссылаются на один и тот же член класса,или оба являются нулевым значением указателя члена
      • или они имеют тип ссылки lvalue и ссылаются на один и тот же объект или функцию
      • или они имеют тип std::nullptr_t
      • или они имеют тип с плавающей точкой и их значения одинаковы
      • или они имеют тип массива (в этом случае массивы должны быть объектами-членами некоторого класса/объединения),и их соответствующие элементы эквивалентны шаблону-аргументу
      • или они относятся к типу union и либо у них обоих нет активного члена,либо у них один и тот же активный член и их активные члены эквивалентны шаблону-argument-equivalent
      • или они имеют тип не объединенного класса и их соответствующие прямые подобъекты и ссылочные члены эквивалентны шаблону-аргументу

      Examples

      Defect reports

      Следующие отчеты о дефектах,изменяющих поведение,были применены ретроактивно к ранее опубликованным стандартам C++.

Похожие статьи